# ShopAgent

基于 **LangGraph** 与 **ReAct 模式** 的自主多模态时尚购物智能体，支持文本与图片输入、商品搜索、风格分析与多轮对话。

## Demo

http://120.76.41.98:6008/


## 概述

ShopAgent 根据用户意图自主决定是否调用工具、调用哪些工具，维护对话状态，并在适当时机给出回复。核心能力包括：

- **自主工具选择与执行**：按需调用 `search_products`、`analyze_image_style`、`web_search`
- **商品搜索**：通过外部 Search API 检索，结果经 LLM 质量评估后写入会话级结果库，回复中通过 `[SEARCH_RESULTS_REF:ref_id]` 引用，前端自动渲染为商品卡片
- **多轮对话与上下文**：LangGraph + MemorySaver 按 `thread_id` 持久化消息
- **图片理解**：上传图片时可调用 `analyze_image_style` 做风格/属性分析，再结合搜索
- **流式输出**：思考过程、工具调用与结果、正式回复以前端可感知的事件流实时展示，调试面板默认展开

## 技术栈

| 组件       | 技术 |
|------------|------|
| Agent 框架 | LangGraph（StateGraph + ReAct） |
| LLM        | LangChain OpenAI 兼容（默认 gpt-4o-mini，可配 base_url 如 DashScope） |
| 推理/思考  | 可选：OpenAI Responses API reasoning / DashScope enable_thinking |
| 搜索       | 外部 Search API（HTTP），会话内结果登记到 SearchResultRegistry |
| 前端       | Streamlit |

## 架构要点

### Agent 流程（LangGraph）

```
START → Agent → [有 tool_calls?] → Tools → Agent → … → END
```

- **Agent 节点**：注入 system prompt，调用 `llm_with_tools.invoke(messages)`，产出 AIMessage（含 content 和/或 tool_calls）。
- **条件边**：若最后一条消息含 `tool_calls` 则进入 **Tools** 节点，否则结束。
- **Tools 节点**：执行本轮工具调用，将 ToolMessage 追加到 state，再次回到 Agent。
- **状态**：`AgentState = { messages, current_image_path }`，`messages` 使用 `add_messages` 累加。

### 搜索结果与引用

- **SearchResultRegistry**：按会话存储每次 `search_products` 的结构化结果（query、质量统计、商品列表），并为每次搜索分配会话内唯一 `ref_id`（如 `sr_1`, `sr_2`）。
- **search_products 工具**：请求 Search API → 用 LLM 对每条结果打标（Relevant / Partially Relevant / Irrelevant）→ 将结果写入 registry，返回包含 `[SEARCH_RESULTS_REF:ref_id]` 的文本及质量摘要。
- **最终回复**：Agent 在正文中写入 `[SEARCH_RESULTS_REF:ref_id]`，前端用正则解析并在对应位置渲染商品卡片（见 `app.py` 中 `SEARCH_RESULTS_REF_PATTERN` 与 `render_message_with_refs`）。

### 工具列表

| 工具 | 说明 |
|------|------|
| `search_products(query)` | 文本商品搜索，会话绑定，结果写入 Registry 并返回 ref 引用 |
| `analyze_image_style(image_path)` | VLM 分析图片风格/属性，供后续搜索或回复使用 |
| `web_search(query)` | 联网检索（如时效、天气、事件等） |

### 流式与调试

- **chat_stream**：`ShoppingAgent.chat_stream(query, image_path)` 迭代产出事件：`debug_update`（思考步骤、工具调用/结果）、`response_delta` / `response_replace`（正式回复增量）、`done`（最终结果）。
- **前端**：发送消息后消费 `chat_stream`，在占位区实时更新工具链、调试步骤和回复；调试面板「思考 & 工具调用详细过程」默认展开，可展示 thinking 与工具结果。
- **chat**：仍保留 `chat()`，内部改为消费 `chat_stream` 直至 `done`，保证兼容。

## 示例

**文本搜索：**
```
用户：「有没有适合通勤的女式大衣」
Agent：search_products("通勤 女式 大衣") → 返回 [SEARCH_RESULTS_REF:sr_1]，页面展示商品卡片
```

**图片 + 搜索：**
```
用户：[上传一张外套图] 「这件是什么风格？找类似款」
Agent：analyze_image_style(path) → 得到风格描述 → search_products("…") → 引用 sr_1 等
```

**多轮与引用：**
```
第1轮：「红色连衣裙」→ search_products → 展示结果
第2轮：「要正式一点的」→ 结合上下文 search_products("红色 正式 连衣裙")
```

**时效类（web_search）：**
```
用户：「最近有什么流行元素」
Agent：web_search("时尚 流行 2025") → 结合结果回复
```

## 安装与运行

### 环境要求

- Python 3.10+（LangChain/LangGraph 推荐 3.12+）
- 配置 `.env`：至少 `OPENAI_API_KEY`；若用自建或第三方 LLM，可设 `OPENAI_API_BASE_URL`（如 DashScope 兼容地址）

### 步骤

```bash
git clone <repository-url>
cd shop_agent
python -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate
pip install -r requirements.txt

# 配置
cp .env.example .env
# 编辑 .env：OPENAI_API_KEY、可选 OPENAI_API_BASE_URL、SEARCH_API_BASE_URL、SEARCH_API_TENANT_ID 等
```

### 可选：本地图片数据

若需本地商品图（如侧边栏「Similar」用本地图），可准备 `data/images/` 下的图片资源；图像风格分析不依赖该目录。

### 启动与日志

```bash
# 推荐：使用服务脚本（含 Conda 环境激活与日志管理）
./scripts/service.sh start      # 后台启动
./scripts/service.sh status     # 查看状态
./scripts/service.sh stop       # 停止

# 本地调试：前台运行（Ctrl+C 停止）
./scripts/service.sh run

# 也可直接运行 Streamlit（需自行激活虚拟环境）
streamlit run app.py
```

- 端口优先级：`STREAMLIT_PORT` 环境变量 > `app.config.settings.app_port` > 默认 `6008`。
- Host 默认为 `0.0.0.0`，可通过 `STREAMLIT_HOST` 覆盖。
- 日志目录：`logs/streamlit.log`（标准输出）、`logs/streamlit_error.log`（错误输出，由 `scripts/service.sh` 统一管理）。

默认访问 `http://<HOST>:<PORT>`（本地通常是 `http://localhost:6008`）。商品搜索依赖外部 Search API，请在 `.env` 中配置 `SEARCH_API_BASE_URL` 与 `SEARCH_API_TENANT_ID`。

### 部署

CentOS 8 等部署说明见 **[docs/DEPLOY_CENTOS8.md](docs/DEPLOY_CENTOS8.md)**（若存在）。

## 配置说明（.env 摘要）

| 变量 | 说明 | 默认 |
|------|------|------|
| `OPENAI_API_KEY` | 必填，LLM/视觉调用 | - |
| `OPENAI_API_BASE_URL` | 可选，兼容接口 base URL（如 DashScope） | - |
| `OPENAI_MODEL` | 对话模型 | gpt-4o-mini |
| `OPENAI_USE_REASONING` | 是否开启思考（OpenAI/DashScope 等） | true |
| `OPENAI_REASONING_EFFORT` | reasoning 力度（如 medium） | medium |
| `SEARCH_API_BASE_URL` | 搜索服务地址 | 见 config.py |
| `SEARCH_API_TENANT_ID` | 租户 ID | 见 config.py |
| `SEARCH_PRODUCTS_LIMIT` | 单次搜索返回条数上限 | 20 |

更多配置项见 `app/config.py` 中的 `Settings`，包括：

- OpenAI 相关：`openai_model`、`openai_vision_model`、`openai_use_reasoning`、`openai_reasoning_effort`、`openai_api_base_url` 等。
- 应用运行：`app_host`、`app_port`、`debug`、`log_level`。
- 数据路径：`raw_data_path`、`processed_data_path`、`image_data_path` 等。

配置加载优先级（高 → 低）：**代码初始化参数 > 项目根目录下 `.env` > 系统环境变量 > 默认值**，其中 `.env` 会覆盖同名系统环境变量（见 `Settings.settings_customise_sources`）。

## 项目结构（概览）

```
shop_agent/
├── app/
│   ├── agents/
│   │   └── shopping_agent.py   # LangGraph 图、chat/chat_stream、thinking 支持
│   ├── config.py              # 配置与环境变量
│   ├── search_registry.py     # SearchResultRegistry、ProductItem、SearchResult
│   ├── tools/
│   │   ├── search_tools.py    # search_products、analyze_image_style、web_search
│   │   └── __init__.py
│   └── ...
├── app.py                     # Streamlit UI、流式渲染、引用解析与商品卡片
├── docs/
│   └── 技术实现报告.md        # 详细设计、Prompt 与架构说明
├── scripts/
│   └── service.sh             # 统一的启停脚本（start/stop/status/run）
├── .env.example
├── requirements.txt
└── README.md
```

---

