Skip to main content

小売とEコマースにおけるAIエージェント

本稿では、AIエージェントが小売をどのように変革しているかを探ります。具体的には、顧客対応の自動化、意思決定の最適化、そしてLLM駆動のベクター検索を用いたレコメンドの強化について解説します。この記事はAIによる翻訳です。誤訳の可能性があれば、コメント欄でお知らせください。
Created on August 27|Last edited on August 27
AIは小売を変革している主要なプロセスを自動化し、 カスタマーサポート パーソナライズされた購買体験へとつながっています。企業は、効率の最適化、エンゲージメントの向上、そしてより賢くデータドリブンな対話を実現するために、AI駆動のシステムを積極的に活用し始めています。こうした技術が進化し続けることで、小売事業者の運用方法は再定義され、顧客の購買体験もオンラインと実店舗の双方で大きく変わりつつあります。
本稿では、…について探っていきます AIエージェント 意思決定の迅速化や自動化に活用されている ワークフロー、顧客とのやり取りをパーソナライズします。さらに、顧客からの問い合わせをより効率的に処理するためのAI搭載メールトリアージシステムと、 レコメンデーションエンジン ベクター検索を用いてパーソナライズされた商品提案を提供する LLM生成クエリ。これらの実装に加えて、小売におけるAIのより広範な影響、すなわち利点、課題、そして知的自動化の進化する役割についても検討します。


目次



AIエージェントとは何か、なぜ小売で重要なのか?

小売におけるAIエージェントは、顧客対応やバックエンドの各種業務を自動化・高度化するための知能システムです。チャットボットや音声アシスタント、パーソナルショッピング体験を支え、顧客のニーズにリアルタイムで適応するシームレスなやり取りを実現します。小売事業者は、パーソナライズされた商品レコメンド、在庫管理、カスタマーサポート、さらには動的価格設定の戦略にもこれらのエージェントを活用できます。
小売分野におけるAIの進化は、基本的なレコメンドシステムから、文脈を理解し、顧客の嗜好を予測し、有意義な対話を行い、意思決定を自動化できる高度なエージェントへと発展してきました。進歩は 自然言語処理メモリの統合やツールの活用により、AIエージェントはデータドリブンな知見と実世界のアクションをつなぐ賢い仲介者として機能します。総じて、AIエージェントの歩みは、ビジネスオペレーションや顧客とのやり取りに自然に溶け込み、売上と顧客満足の双方を高めていく未来を示しています。
その機能の中核を成すのは、いくつかの主要コンポーネントです。
  • ツール:AIエージェントはAPI、データベース、各種ソフトウェアと連携し、商品カタログ、受注管理プラットフォーム、物流ネットワークへアクセスして、シームレスなオペレーションを実現します。
  • メモリ顧客の嗜好や過去のやり取りを保持し、パーソナライズされたレコメンドや高いエンゲージメント、そして一貫したショッピング体験を実現します。
  • 継続学習:顧客行動、市場動向、事業パフォーマンスを分析することで、AIエージェントは時間とともに戦略を洗練し、精度と有効性を高めていきます。
  • オーケストレーション:これらのエージェントは複雑な小売プロセスを管理し、受注処理、サプライヤーとの調整、リアルタイムの意思決定を自動化して効率を高めます。
自然言語処理、マルチモーダルAI、適応学習の進展が続くにつれ、AIエージェントは小売オペレーションにますます不可欠な存在となり、売上を牽引し、顧客満足度を高め、業務プロセスの効率化を促進します。

小売事業者にとってのAIエージェントの主な利点

AIエージェントは、従来型システムの複雑さを抑え、小売のオペレーションをシンプルにします。これまで小売事業者は、市場の変化に対応するために、手作業のルール設定や大規模なエンジニアリング、頻繁な調整に頼ってきました。大規模言語モデルに支えられたAIシステムは、データを動的に解釈し、インサイトを生成し、最小限の人手で意思決定を自動化します。この適応性により、絶え間ない再プログラミングを行わなくても、変化するトレンドや顧客行動、運用上の課題に迅速に対応できるようになります。
AIの大きな利点のひとつは、顧客ごとに最適化されたやり取りです。AIによるレコメンド、 チャットボット、および 仮想アシスタント 製品の提案やプロモーション、サポート対応を個々の嗜好に合わせて最適化します。従来のレコメンドエンジンが取引データのみに依存していたのに対し、最新のAIモデルは閲覧履歴や購買パターン、さらには自然言語でのやり取りまで分析し、文脈に応じたレコメンドを提供します。こうした高度なパーソナライズにより、コンバージョン率が向上し、顧客維持率が高まり、ブランドロイヤルティが強化されます。
AIは、よくある問い合わせへの回答を自動化し、重要度の高い課題を優先処理することで、カスタマーサービスも変革します。AI搭載のメールトリアージシステムは、メッセージを分類し、緊急度を判定し、差し迫った懸案をリアルタイムにエスカレーションできます。チャットボットや音声アシスタントは定型的な質問に対応し、人間のエージェントの負荷を軽減しつつ、顧客に迅速・正確・一貫したサポートを提供します。こうした自動化により、応答時間が短縮され、運用コストが下がり、従業員は共感や問題解決が求められる高付加価値のやり取りに集中できるようになります。
これらの重要な機能を自動化することで、AIは運用のオーバーヘッドを削減し、効率を高め、小売全体でデータに基づく意思決定を強化します。

AIエージェントの中核コンポーネントと基盤技術

AIエージェントは、LLM、メモリ、外部ツール、そしてオーケストレーションシステムを用いてデータを処理し、意思決定を行い、適応します。中核となるのはLLMで、人間らしい言語を理解・生成できるため、顧客対応や意思決定に不可欠です。これらのエージェントは、適応性と精度を担保する相互接続されたシステムの中で動作します。
AIエージェントの主な構成要素は次のとおりです。
  • ツール統合:AIエージェントは外部システムと接続し、リアルタイムのデータにアクセスして、顧客からの質問に回答するなどのタスクを実行します。たとえば、ShopifyのAPIと統合したAIエージェントは製品の在庫状況を即時に確認でき、Salesforceと接続したエージェントは顧客の購買履歴を取得して、パーソナライズされたレコメンドを提供できます。
  • メモリ:過去のやり取りを想起できるようにし、一貫性のあるパーソナライズされた応答を実現します。例えば、バーチャルショッピングアシスタントは前回のセッションでの顧客の嗜好を記憶しており、顧客が最初からやり直すことなく、シームレスに製品をレコメンドできます。
  • オーケストレーション:複数のAI機能を連携させて複雑な小売のワークフローを管理します。注文処理、サプライヤーとの調整、リアルタイムの意思決定などを含み、オペレーションを効率化して手作業を減らします。
  • オブザーバビリティ:AIエージェントの透明性、デバッグ、継続的な最適化を実現します。Weights & Biases Weave のようなプラットフォームを使えば、小売企業は応答の正確性を追跡し、ハルシネーションを検知し、AIの振る舞いをリアルタイムで改善できます。これにより、性能と信頼性の双方が向上します。
これらの要素が揃うことで、AIエージェントは文脈を理解した信頼性の高いスケーラブルな自動化を実現し、小売のオペレーションを変革して顧客体験を向上させます。

小売向けエージェントにおけるLLMの役割

大規模言語モデル(LLM)は現代のAIエージェントを支える中核技術であり、言語の理解と生成、そして実世界のやり取りへの柔軟な適応を可能にします。従来のルールベース型AIとは異なり、LLMはディープラーニングと膨大なデータセットを活用して、テキストを動的に予測・理解・生成します。自然な対話の処理、情報要約、パーソナライズされたレコメンドといった能力により、AIエージェントは単なる自動化ツールから、知的に意思決定できる存在へと進化しました。
LLMの進化により、小売におけるAIエージェントは一層高い応答性と効果を発揮するようになりました。少数ショット学習のような手法により、 検索拡張生成(RAG)、および ファインチューニング これらのモデルは、ビジネスのニーズに適応し、パーソナライズされた顧客対応を提供し、人手を介さずにサポートを自動化できるようにします。
LLMの進化が続くにつれ、小売テクノロジーの可能性はさらに広がり、より賢い自動化、より深いパーソナライズ、そして一層効率的なオペレーションが実現されていきます。

セキュリティ、コンプライアンス、スケーラビリティ

小売のユースケースでは、AIエージェントは購買履歴や決済情報などの機微な顧客データを扱うため、セキュリティと法規制への準拠が極めて重要です。信頼を維持し法的リスクを回避するには、GDPRやCCPAなどのデータ保護法を順守し、暗号化や安全な保存、ユーザー同意の仕組みを実装する必要があります。とりわけパーソナライズのような領域でAI主導のシステムが不正利用されることを防ぐには、厳格なアクセス制御が不可欠です。 不正検知、および決済処理。
法令順守にとどまらず、AIシステムは サイバー脅威 意思決定を損なうおそれがあります。敵対的攻撃により、モデルが誤った予測を下すよう誘導される一方で、 プロンプトインジェクション 誤情報の拡散や不正なデータアクセスにつながるおそれがあります。これらのリスクを軽減するには、リアルタイムの異常検知と自動クエリフィルタリングが重要です。AIによる監視ツールは、通常と異なる挙動を検知し、異常を特定して、深刻化する前にセキュリティ侵害を未然に防ぐことができます。
信頼を築くには、AIエージェントの透明性と監査可能性が欠かせません。高度な監視システムは、意思決定の過程を追跡し、やり取りを記録し、不整合をリアルタイムで検知します。小売企業は、他のAIエージェントを監督するAIエージェントを活用し、回答の正確性や方針順守を保ち、幻覚(ハルシネーション)を防ぐこともできます。たとえば、プラットフォームとしては W&B Weave モデルのパフォーマンスを追跡し、回答を改善し、システムの信頼性を維持することで、監督体制を強化します。適切な監視がなければ、AIエージェントは誤解を招く回答を生成したり、偏ったレコメンドを行ったり、機密情報を露出させてしまい、ブランドの評判を損なうおそれがあります。
スケーラビリティも大きな課題であり、特にブラックフライデーのようなピーク時には顕在化します。AIエージェントは、トラフィックの急増に対応し、取引を処理し、在庫をリアルタイムで管理しなければなりません。効率的にスケールできないと、システムの遅延や取引失敗、顧客体験の低下を招くおそれがあります。検索拡張生成(RAG)のような手法は、大規模モデルへのフルクエリ依存を減らして効率を高め、応答を高速かつコスト効率よくします。さらに、モデルの量子化は、精度を犠牲にせずAIモデルを圧縮し、需要が高い状況でも効率的にスケールできるようパフォーマンスを強化します。
大規模にAIを導入する小売企業にとって、セキュリティ、コンプライアンス、パフォーマンスは「選択肢」ではなく不可欠な要件です。AI主導のシステムは、常時監視と最適化を行い、セキュアで、法令や規程に準拠し、スケール可能な状態を保たなければなりません。これらが欠けると、 セーフガードが整っていなければ、AIエージェントは資産ではなく負債となり、本来提供すべき効率化やパーソナライズを損ないかねません。

チュートリアル:AIによるメール優先度付けの自動化

小売企業には、技術的な不具合、請求に関する異議、機能要望、一般的な問い合わせなど、多数の顧客メールが寄せられます。これらを人手で確認・分類するのは非効率で、重要案件への対応が遅れる原因にもなります。AIによる自動化は、メールの内容を解析して緊急度を判定し、最も急を要する案件から優先的に対応できるようにすることで、このプロセスを効率化します。
このチュートリアルでは、次の機能を備えたAI主導のメール優先度付けシステムを構築します。
  • 言語的手がかりと業務への影響を解析して緊急度を判定し、HIGH・MEDIUM・LOW の優先度を割り当てます。
  • 技術的な不具合、請求、連携、カスタマーサービスなど、あらかじめ定義した業務カテゴリにメールを分類します。
  • 緊急度とタイムスタンプに基づいてメールを優先順位付け・ランク付けし、重要案件から先に対応できるようにします。
  • よくある顧客の懸念事項に関するレポートを生成し、製品やサービスの最適化に役立てます。
W&B Weave によるリアルタイム監視でこのシステムは透明性を保ちつつ、優先度ロジックを動的に洗練し、小売のオペレーションにおいて効率的にスケールします。
次のコード AI主導のシステムがメールを動的に解析・分類・優先順位付けする方法を示します。これは次のものを使用します。 OpenAIのGPT-4o 推論にはを、リアルタイム監視にはWeaveを使用します。本チュートリアルではモックデータセットを使いますが、実運用ではAPI経由で実際の受信トレイに接続できます。
import json
from datetime import datetime, timedelta
from litellm import completion
import os
import weave; weave.init('retail-email-agent')


os.environ["OPENAI_API_KEY"] = "your api key"


# Mock email database
MOCK_EMAILS = [
{
"id": "1",
"from": "customer@example.com",
"subject": "App keeps crashing",
"body": "The mobile app has been crashing constantly for the past week. This is frustrating as I rely on it for work.",
"timestamp": (datetime.now() - timedelta(hours=2)).isoformat(),
"urgency": None,
"category": None
},
{
"id": "2",
"from": "support@partner.com",
"subject": "Urgent: Service integration issue",
"body": "We're experiencing problems with the API integration. Several customers are affected. Need immediate assistance.",
"timestamp": (datetime.now() - timedelta(hours=1)).isoformat(),
"urgency": None,
"category": None
},
{
"id": "3",
"from": "user123@example.com",
"subject": "Billing overcharge",
"body": "I was charged twice for my monthly subscription. Please refund the extra charge. Your billing system needs better validation.",
"timestamp": (datetime.now() - timedelta(minutes=30)).isoformat(),
"urgency": None,
"category": None
},
{
"id": "4",
"from": "enterprise@bigcorp.com",
"subject": "Missing enterprise features",
"body": "We need better team management features and role-based access control. Currently managing large teams is very manual.",
"timestamp": (datetime.now() - timedelta(minutes=45)).isoformat(),
"urgency": None,
"category": None
}
]

# Urgency levels mapped to descriptors
URGENCY_LEVELS = {
2: "HIGH", # Critical issues, system down, multiple users affected
1: "MEDIUM", # Important but not critical
0: "LOW" # Regular requests, no immediate impact
}

# Categories mapped to integers
CATEGORIES = {
0: "Technical Issues", # App crashes, bugs
1: "Performance", # Speed, reliability
2: "Billing/Pricing", # Payment problems
3: "User Experience", # UI/UX issues
4: "Feature Requests", # New features
5: "Security/Privacy", # Security concerns
6: "Integration", # API issues
7: "Customer Service", # Support general
8: "Documentation", # Help docs
9: "Enterprise" # Large customer needs
}

class EmailSystem:
def __init__(self, model_id="openai/gpt-4o"):
self.emails = MOCK_EMAILS.copy()
self.model_id = model_id
def _run_inference(self, prompt):
"""Run inference using the specified model"""
try:
response = completion(
model=self.model_id,
messages=[{"role": "user", "content": prompt}],
temperature=0.1
)
return response["choices"][0]["message"]["content"].strip()
except Exception as e:
print(f"Inference error: {str(e)}")
return "7" # Default to Customer Service
def get_category(self, email):
"""Get category as integer 0-9"""
categories = "\n".join(f"{k}: {v}" for k, v in CATEGORIES.items())
prompt = f"""Categorize this email into exactly ONE category by responding with ONLY its number (0-9):

Categories:
{categories}

Email subject: {email['subject']}
Email body: {email['body']}

Respond with ONLY a single number 0-9:"""
try:
response = self._run_inference(prompt)
category = int(response)
if category not in CATEGORIES:
return 7 # Default to Customer Service
return category
except:
return 7
@weave.op
def get_urgency(self, email):
"""Get urgency as integer 0-2"""
prompt = f"""Rate this email's urgency with ONLY ONE number:
2: HIGH - Emergency, system down, multiple users affected
1: MEDIUM - Important issue but not critical
0: LOW - Regular request, no immediate impact

Email subject: {email['subject']}
Email body: {email['body']}

Respond with ONLY the number (2, 1, or 0):"""
try:
response = self._run_inference(prompt)
urgency = int(response)
if urgency not in [0, 1, 2]:
return 0
return urgency
except:
return 0

def process_emails(self):
"""Process all emails and return summary"""
urgency_map = {2: "HIGH", 1: "MEDIUM", 0: "LOW"}
results = []
for email in self.emails:
# Get category
category_num = self.get_category(email)
category_name = CATEGORIES[category_num]
# Get urgency
urgency_num = self.get_urgency(email)
urgency_name = urgency_map[urgency_num]
results.append({
"subject": email["subject"],
"category_num": category_num,
"category": category_name,
"urgency_num": urgency_num,
"urgency": urgency_name
})
return results

class UrgencyRater:
def __init__(self, model_id="openai/gpt-4o"):
self.model_id = model_id
def _run_inference(self, prompt):
"""Run inference using the specified model"""
try:
response = completion(
model=self.model_id,
messages=[{"role": "user", "content": prompt}],
temperature=0.1
)
return response["choices"][0]["message"]["content"].strip()
except Exception as e:
print(f"Inference error: {str(e)}")
return "0" # Default to LOW urgency
@weave.op
def rate_urgency(self, email):
"""Rate email urgency from 0-2"""
prompt = f"""Analyze this email's urgency and respond with ONLY ONE number:

2: HIGH URGENCY - Critical issues:
- System down/major outage
- Multiple users/customers affected
- Security incidents
- Significant revenue impact
- Words like "urgent", "emergency", "immediate"

1: MEDIUM URGENCY - Important issues:
- Single user blocked
- Bug affecting functionality
- Billing problems
- Integration issues
- Performance problems

0: LOW URGENCY - Regular requests:
- Feature requests
- Documentation
- General questions
- Non-blocking issues
- Future planning

Email subject: {email['subject']}
Email body: {email['body']}

Respond with ONLY the number (2, 1, or 0):"""
try:
response = self._run_inference(prompt)
urgency = int(response)
if urgency not in URGENCY_LEVELS:
return 0
return urgency
except:
return 0
def batch_rate_emails(self, emails):
"""Rate urgency for a batch of emails"""
results = []
for email in emails:
urgency_num = self.rate_urgency(email)
results.append({
"subject": email["subject"],
"urgency_num": urgency_num,
"urgency": URGENCY_LEVELS[urgency_num],
"timestamp": email["timestamp"]
})
# Sort by urgency (high to low) and then by timestamp
return sorted(results,
key=lambda x: (-x["urgency_num"], x["timestamp"]))

if __name__ == "__main__":
# Test both systems
print("=== Category System Test ===")
category_system = EmailSystem()
category_results = category_system.process_emails()
for r in category_results:
print(f"\nSubject: {r['subject']}")
print(f"Category: {r['category_num']} ({r['category']})")
print(f"Urgency: {r['urgency_num']} ({r['urgency']})")
print("\n=== Urgency Rater Test ===")
urgency_system = UrgencyRater()
urgency_results = urgency_system.batch_rate_emails(MOCK_EMAILS)
for r in urgency_results:
print(f"\nSubject: {r['subject']}")
print(f"Urgency: {r['urgency_num']} ({r['urgency']})")
print(f"Timestamp: {r['timestamp']}")
このシステムは、LLMを用いて顧客メールを分類し、緊急度を割り当てて処理します。メールが届くと、件名と本文を含むプロンプトを生成してLLMに渡します。モデルは、カテゴリID(例:請求、技術的問題、カスタマーサービス)と緊急度スコアで応答します。LOWMEDIUM、または HIGH)あらかじめ定義された基準に基づいて
その EmailSystem このクラスは、構造化プロンプトでモデルを呼び出し、分類と緊急度スコアリングを処理します。 UrgencyRater このクラスは、高優先度を示すキーフレーズや文脈を検出して緊急度の分類を精緻化します。分類後は、メールを緊急度とタイムスタンプで並べ替え、請求の紛争やシステム障害のような差し迫った課題に即時対応できるようにします。
効率化のために、このシステムは統合されています Weave リアルタイム監視に対応しており、AIのパフォーマンスの追跡、異常検知、優先度付けロジックの動的な改善が可能です。本チュートリアルでは模擬データセットを使用しますが、APIを介して実際の受信トレイに接続すれば、実際の顧客メールを自動処理でき、手作業を減らして応答時間を短縮できます。
メール解析にAIを活用することで、小売企業は効率的かつ先回りのカスタマーサービスを実現し、最終的に顧客満足度と業務効率を高められます。

小売向けAI搭載プロダクトレコメンドシステム

小売企業は、パーソナライズされたレコメンドで顧客のエンゲージメントと売上を高めています。AI主導のレコメンドシステムは、商品の類似性、閲覧行動、過去のインタラクションを分析し、関連性の高いアイテムを提案します。本チュートリアルでは、Burberry の製品カタログを掲載した模擬小売サイトを作成し、AI搭載のレコメンドシステムを組み込みます。
こちらが、サイトの見た目のスクリーンショットです。

このサイトでは、ユーザーがカテゴリを閲覧し、個別の商品ページを表示し、操作内容に基づいた動的なレコメンドを受け取れます。これを実現するために、Burberry の商品データセット(商品名、画像、価格、カテゴリ情報を含む)を使用します。レコメンドエンジンは、ユーザーの閲覧行動を追跡し、大規模言語モデルを用いて検索クエリを生成し、ベクターデータベースから類似商品を取得します。
これらのレコメンドを実現するために、まず OpenAI の text-embedding モデルで生成した商品ベクトル(埋め込み)を格納するベクターデータベースを作成します。これにより、単なるキーワードではなく意味的な近さに基づいて商品を比較できます。次に、これを Flask ベースの Web アプリケーションに統合し、ユーザーが商品を閲覧し、パーソナライズされたレコメンドを受け取れるようにします。
また、AIがレコメンドをどのように選定・ランキングしているかを追跡するために Weave を使用し、透明性の確保と継続的な最適化を実現します。LLM 生成の検索クエリ、ベクターによる類似度検索、リアルタイムのトラッキングを組み合わせることで、このシステムは高い適応性を備えたパーソナライズドな購買体験を提供します。

ベクターデータベースの構築

効果的なレコメンドを行うには、単純なキーワード一致を超えて商品を比較できる仕組みが必要です。そこで、OpenAI の text-embedding モデルを用いて商品説明を埋め込みベクトルに変換します。変換した埋め込みはインメモリの ベクターデータベースし、類似度に基づく効率的な検索を可能にします。
このプロセスでは、商品データを読み込み、説明文を数値ベクトルに変換し、後で使えるようにメタデータを保存します。保存した埋め込みにより、テキストの完全一致ではなく意味的な近さに基づいて関連商品を見つけられるようになります。
import os
import pandas as pd
import pickle
from datasets import load_dataset
from langchain_openai import OpenAIEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.documents import Document

# Set up OpenAI API Key (Replace with your actual API key)
os.environ["OPENAI_API_KEY"] = "your api key"

# Load dataset from Hugging Face
dataset = load_dataset('DBQ/Burberry.Product.prices.United.States')
df = pd.DataFrame(dataset['train'])

# Drop missing values
df = df.dropna(subset=["title", "imageurl", "category2_code", "category3_code", "product_code"])

# Convert product descriptions into embeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
documents = [
Document(
page_content=f"{row['title']} - Category: {row['category2_code']} > {row['category3_code']}",
metadata={
"title": row["title"],
"imageurl": row["imageurl"],
"price": row["price"],
"category": row["category2_code"],
"product_code": str(row["product_code"])
}
) for _, row in df.iterrows()
]

# Store only document data (not the full vector store)
with open("documents.pkl", "wb") as f:
pickle.dump(documents, f)

# Save metadata separately
with open("metadata.pkl", "wb") as f:
pickle.dump(df, f)

print("Documents and metadata saved successfully! You can now run the Flask app.")


Webサイトとレコメンドシステムの構築

ベクターデータベースの構築が完了すると、システムはパーソナライズされたレコメンドエンジンを駆動します。
Flask を使って、ユーザーが商品カテゴリを閲覧し、個別の商品ページを表示し、AI によるレコメンドを受け取れるシンプルなフロントエンドを構築します。サイトは Burberry の商品カタログを動的に整理し、画像、価格、詳細を直感的なインターフェースで表示します。ユーザーが商品を閲覧すると、言語モデルで検索クエリを生成して、類似アイテムを取得します。このクエリは、これまでに閲覧したすべての商品、または現在選択中の商品のいずれかに基づきます。続いて、ベクターデータベースから最も関連性の高い商品を検索し、商品詳細ページに表示するためにランキングします。これにより、パーソナライズされたシームレスな商品発見を実現します。
Webサイトのコードは次のとおりです。
from flask import Flask, request, render_template_string
import pickle
import os
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_openai import OpenAIEmbeddings
import random
from langchain.chat_models import ChatOpenAI
import weave; weave.init("retail-agent")

# Initialize LLM
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)

# Set up OpenAI API Key
os.environ["OPENAI_API_KEY"] = "your api key"


app = Flask(__name__)

# Load stored documents (precomputed embeddings)
with open("documents.pkl", "rb") as f:
documents = pickle.load(f)

# Recreate the vector store
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vector_store = InMemoryVectorStore(embeddings)
_ = vector_store.add_documents(documents)

# Load metadata (original product details)
with open("metadata.pkl", "rb") as f:
df = pickle.load(f)

# Track viewed products
viewed_products = []

# Home page template
home_template = """
<!DOCTYPE html>
<html>
<head>
<title>Product Categories</title>
<style> body { font-family: Arial, sans-serif; } </style>
</head>
<body>
<h1>Product Categories</h1>
<ul>
{% for category in categories %}
<li><a href="{{ url_for('subcategory_page', category=category) }}">{{ category }}</a></li>
{% endfor %}
</ul>
</body>
</html>
"""

# Subcategory page template
subcategory_template = """
<!DOCTYPE html>
<html>
<head>
<title>{{ category }} Subcategories</title>
<style> body { font-family: Arial, sans-serif; } </style>
</head>
<body>
<h1>{{ category }} Subcategories</h1>
<ul>
{% for subcategory in subcategories %}
<li><a href="{{ url_for('product_page', category=category, subcategory=subcategory) }}">{{ subcategory }}</a></li>
{% endfor %}
</ul>
</body>
</html>
"""

# Product listing page template
product_template = """
<!DOCTYPE html>
<html>
<head>
<title>{{ subcategory }} Products</title>
<style>
body { font-family: Arial, sans-serif; }
.product { display: flex; align-items: center; margin-bottom: 20px; }
.product img { width: 100px; margin-right: 10px; }
</style>
</head>
<body>
<h1>{{ subcategory }} Products</h1>
<ul>
{% for product in products %}
<li class="product">
<a href="{{ url_for('product_detail', category=category, subcategory=subcategory, product_id=product['product_code']) }}">
<img src="{{ product['imageurl'] }}" alt="{{ product['title'] }}">
</a>
<div>
<p><strong>{{ product['title'] }}</strong></p>
<p>Price: ${{ product['price'] }}</p>
</div>
</li>
{% endfor %}
</ul>
</body>
</html>
"""
product_detail_template = """
<!DOCTYPE html>
<html>
<head>
<title>{{ product['title'] }}</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
.page-container {
display: flex;
gap: 30px;
max-width: 1400px;
margin: 0 auto;
}
.product-container {
flex: 0 0 600px;
position: sticky;
top: 20px;
align-self: flex-start;
}
.product-container img {
width: 100%;
max-width: 500px;
height: auto;
}
.recommendations {
flex: 1;
min-width: 0;
}
.recommendations-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
margin-top: 20px;
}
.recommendation-item {
text-align: center;
}
.recommendation-item img {
width: 100%;
height: 200px;
object-fit: contain;
}
.recommendation-item p {
margin: 5px 0;
font-size: 14px;
}
.back-link {
display: block;
margin-top: 20px;
}
h1 {
font-size: 24px;
margin-bottom: 20px;
}
h2 {
font-size: 20px;
margin-bottom: 15px;
}
</style>
</head>
<body>
<div class="page-container">
<div class="product-container">
<h1>{{ product['title'] }}</h1>
<img src="{{ product['imageurl'] }}" alt="{{ product['title'] }}">
<p><strong>Category:</strong> {{ product['category2_code'] }} - {{ product['category3_code'] }}</p>
<p><strong>Price:</strong> ${{ product['price'] }}</p>
<a href="{{ url_for('product_page', category=category, subcategory=subcategory) }}" class="back-link">Back to Products</a>
</div>

<div class="recommendations">
<h2>Recommended Products</h2>
<div class="recommendations-grid">
{% for rec in recommendations %}
<div class="recommendation-item">
<a href="{{ url_for('product_detail', category=rec['category'], subcategory=rec['subcategory'], product_id=rec['product_code']) }}">
<img src="{{ rec['imageurl'] }}" alt="{{ rec['title'] }}">
</a>
<p><strong>{{ rec['title'] }}</strong></p>
<p>${{ rec['price'] }}</p>
</div>
{% endfor %}
</div>
</div>
</div>
</body>
</html>
"""
@app.route("/")
def home():
"""Display the main categories."""
categories = df["category2_code"].dropna().unique()
return render_template_string(home_template, categories=categories)

@app.route("/category/<category>")
def subcategory_page(category):
"""Display subcategories under a main category."""
subcategories = df[df["category2_code"] == category]["category3_code"].dropna().unique()
return render_template_string(subcategory_template, category=category, subcategories=subcategories)

@app.route("/category/<category>/<subcategory>")
def product_page(category, subcategory):
"""Display products under a specific subcategory."""
products = df[(df["category2_code"] == category) & (df["category3_code"] == subcategory)][["title", "imageurl", "price", "product_code"]].head(20).to_dict(orient="records")
return render_template_string(product_template, category=category, subcategory=subcategory, products=products)



@app.route("/category/<category>/<subcategory>/<product_id>")
def product_detail(category, subcategory, product_id):
"""Display product details with recommendations from two separate LLM queries."""
product = df[df["product_code"].astype(str) == product_id].iloc[0].to_dict()

# Add product to viewed list
if product not in viewed_products:
viewed_products.append(product)

# Generate two separate recommendation queries
search_query_all_products = generate_recommendation_query(all_products=True)
search_query_current_product = generate_recommendation_query(current_product=product)

recommended_products = []

# Fetch general recommendations from all viewed products
if search_query_all_products:
search_results_all = vector_store.similarity_search(search_query_all_products, k=50)
sampled_results_all = random.sample(search_results_all, min(10, len(search_results_all)))

recommended_products.extend([
{
"title": doc.metadata.get("title", "Unknown Title"),
"imageurl": doc.metadata.get("imageurl", ""),
"price": doc.metadata.get("price", "N/A"),
"category": doc.metadata.get("category2_code", "Unknown"),
"subcategory": doc.metadata.get("category3_code", "Unknown"),
"product_code": doc.metadata.get("product_code", "Unknown")
}
for doc in sampled_results_all
])

# Fetch 5 recommendations similar to the currently viewed product
if search_query_current_product:
search_results_current = vector_store.similarity_search(search_query_current_product, k=5)
recommended_products.extend([
{
"title": doc.metadata.get("title", "Unknown Title"),
"imageurl": doc.metadata.get("imageurl", ""),
"price": doc.metadata.get("price", "N/A"),
"category": doc.metadata.get("category2_code", "Unknown"),
"subcategory": doc.metadata.get("category3_code", "Unknown"),
"product_code": doc.metadata.get("product_code", "Unknown")
}
for doc in search_results_current
])

return render_template_string(product_detail_template, category=category, subcategory=subcategory, product=product, recommendations=recommended_products)

@weave.op
def generate_recommendation_query(all_products=False, current_product=None):
"""Generate search queries for product recommendations using an LLM."""
if all_products and viewed_products:
sampled_products = random.sample(viewed_products, min(3, len(viewed_products)))
product_descriptions = [
f"Product: {p.get('title', '')}, Category: {p.get('category2_code', '')}, Subcategory: {p.get('category3_code', '')}"
for p in sampled_products
]
prompt = f"""
The user has viewed the following products:
{', '.join(product_descriptions)}

Based on these products, generate a concise search query to find similar items with related styles, names, or categories.
You can suggest other product categories that the user might be interested in.
RESPOND ONLY with the query.
"""
elif current_product:
prompt = f"""
The user is currently viewing:
Product: {current_product.get('title', '')}, Category: {current_product.get('category2_code', '')}, Subcategory: {current_product.get('category3_code', '')}

Generate a concise search query to find similar products with related styles, names, or categories.
RESPOND ONLY with the query.
"""
else:
return None

response = llm.invoke(prompt)
return response.content.strip()


if __name__ == "__main__":
app.run(debug=True)

このシステムは閲覧履歴を追跡し、過去の行動が今後の提案に反映されるようレコメンドを精緻化します。従来のレコメンドシステムが協調フィルタリングや固定ルールのみに依存するのとは異なり、このシステムは LLM を用いて検索クエリを動的に生成します。単に購入頻度の高い商品を機械的にマッチさせるのではなく、LLM が閲覧された商品の意味を解釈し、ユーザーの閲覧行動に合わせて最適化した検索クエリを生成します。
このシステムはセッション内で閲覧済みの商品を追跡し、LLM を用いて2種類のレコメンド用検索クエリを生成します。1つ目のクエリは、ユーザーの閲覧履歴からランダムに最大3点の商品を選び、その商品説明を基に共通するテーマ、スタイル、カテゴリを捉えた検索クエリを生成します。2つ目のクエリは、現在閲覧中の商品のみから生成し、その属性に基づいた精度の高いレコメンドを実現します。
これらのクエリを作成した後、商品データベースに対してベクトル類似度検索を実行します。最初のクエリでは、最大50件の一致商品を取得し、その中からランダムに10件を選んで表示し、バリエーションを持たせます。2つ目のクエリでは、ベクトル類似度のみに基づいて、最も関連性の高い5件を取得します。この手法により、汎化(全体的な閲覧行動に基づくレコメンド)と特異性(現在の商品に基づくレコメンド)のバランスが取れ、より関連性が高くパーソナライズされた商品提案を実現します。
私たちのレコメンドシステムの大部分は、主に次の箇所に明確に表れています generate_recommendation_query 本質的に、ベクターデータベースの検索に用いるクエリを生成する関数です。
def generate_recommendation_query(all_products=False, current_product=None):
"""Generate search queries for product recommendations using an LLM."""
if all_products and viewed_products:
sampled_products = random.sample(viewed_products, min(3, len(viewed_products)))
product_descriptions = [
f"Product: {p.get('title', '')}, Category: {p.get('category2_code', '')}, Subcategory: {p.get('category3_code', '')}"
for p in sampled_products
]
prompt = f"""
The user has viewed the following products:
{', '.join(product_descriptions)}


Based on these products, generate a concise search query to find similar items with related styles, names, or categories.
You can suggest other product categories that the user might be interested in.
RESPOND ONLY with the query.
"""
elif current_product:
prompt = f"""
The user is currently viewing:
Product: {current_product.get('title', '')}, Category: {current_product.get('category2_code', '')}, Subcategory: {current_product.get('category3_code', '')}


Generate a concise search query to find similar products with related styles, names, or categories.
RESPOND ONLY with the query.
"""
else:
return None


response = llm.invoke(prompt)
return response.content.strip()
これらのやり取りを追跡し、時間とともにレコメンドを精緻化するために、システムは Weave を統合し、検索クエリ、ユーザーのイン��ラクション、レコメンド結果を記録します。これを追加することで、 @weave.op このデコレータをLLMの推論関数に追加すると、関数へのすべての入力と出力をWeave内で追跡できます。LLMが検索クエリを生成するたびに、Weaveがその過程を記録し、リアルタイムでのモニタリングと調整を可能にします。こうしたトラッキングによりレコメンドシステムの透明性が保たれ、ユーザー嗜好のパターンを検出しやすくなり、検索クエリの精度向上に向けた洗練が進みます。
こちらは、ウェブサイトに掲載されている Weave のトレースのスクリーンショットです。

検索クエリの生成に LLM を用いると、従来のレコメンド手法に比べて多くの利点があります。たとえば、協調フィルタリングのような手法は大量のユーザーデータを必要とし、新規やニッチな商品の取り扱いが苦手です。これに対し、LLM 主導のアプローチは硬直的なパターンではなく「意味」に基づいてレコメンドできるため、柔軟性が高く、応答性にも優れます。文脈を踏まえたクエリを生成できるため、閲覧履歴がほとんどないユーザーにも的確な提案を行えます。AI と Weave によるリアルタイムのトラッキングを活用すれば、小売は適応力が高く、示唆に富み、エンゲージメントと売上の向上に効果的なレコメンドシステムを構築できます。

結論

AIエージェントは、オペレーションの自動化、顧客エンゲージメントの強化、リアルタイムでのパーソナライズ体験の提供によって、小売を大きく変革しています。メールのトリアージから高度なレコメンドシステムまで、AI主導のソリューションは業務を効率化し、意思決定を改善し、シームレスな顧客体験を実現します。静的なルールベースの仕組みから適応的なAIエージェントへの移行により、小売は俊敏性を保ち、人手を最小限に抑えながらワークフローを最適化できるようになります。
このプロジェクトのAI搭載レコメンドシステムは、ベクター検索やLLM生成クエリ、そしてWeaveによるリアルタイムのトラッキングは、高度にパーソナライズされた購買体験を実現します。ユーザー行動の理解を継続的に磨き上げることで、AIは商品の発見性を高め、顧客満足度を向上させます。
AI技術の進化に伴い、小売への影響はますます大きくなっていきます。会話型AIや予測分析から自律型のショッピングアシスタントに至るまで、AI搭載のソリューションは顧客とブランドの関わり方そのものを再定義するでしょう。これらの革新を取り入れる小売企業は、効率と売上を高めるだけでなく、パーソナライズされ知的な購買体験の新たな標準を築くことになります。

Iterate on AI agents and models faster. Try Weights & Biases today.