15 KiB
Search Agent 迁移计划
将
search_agent/项目按照agent_templates/模板结构进行重构
架构变更图
迁移前后目录结构对比
graph LR
subgraph 迁移前
A1[search_agent/]
A1 --> A2[api.py]
A1 --> A3[config.py]
A1 --> A4[main.py]
A1 --> A5[agent/]
A1 --> A6[models/]
A1 --> A7[modules/]
A1 --> A8[tools/]
A1 --> A9[utils/]
end
subgraph 迁移后
B1[search_agent/]
B1 --> B2[Dockerfile]
B1 --> B3[run_api_server.py]
B1 --> B4[src/]
B4 --> B5[server/]
B5 --> B6[api_server.py]
B5 --> B7[mcp_server.py]
B5 --> B8[core/]
B8 --> B9[config.py]
B8 --> B10[agent.py]
B8 --> B11[modules/]
B8 --> B12[tools/]
B8 --> B13[utils/]
end
API 端点变更
graph TB
subgraph 原 API
O1[GET /]
O2[GET /health]
O3[POST /search]
O4[GET /config]
end
subgraph 新 API
N1[GET /]
N2[GET /health]
N3[POST /mcp]
N4[GET /mcp/sse]
N5[POST /mcp/sse]
N6[POST /api/v1/search]
N7[POST /api/v1/quick_search]
end
O1 -.-> N1
O2 -.-> N2
O3 -.-> N6
O4 -.删除.-> X[X]
MCP 工具调用流程
sequenceDiagram
participant Client
participant API as api_server.py
participant MCP as mcp_server.py
participant Agent as SearchAgent
participant Tools as External APIs
Client->>API: POST /mcp tools/call search
API->>MCP: TOOL_MAP search
MCP->>Agent: agent.search query
Agent->>Tools: Serper/Jina APIs
Tools-->>Agent: 搜索结果
Agent-->>MCP: AgentResponse
MCP-->>API: JSON result
API-->>Client: MCP Response
概述
将 search_agent/ 项目按照 agent_templates/ 模板结构进行重构,使其符合统一的 Agent 项目规范。
当前结构 vs 目标结构
当前 search_agent 结构
search_agent/
├── .env # 环境变量配置
├── api.py # FastAPI 服务(独立文件)
├── config.py # 配置管理
├── main.py # CLI 入口
├── requirements.txt # 依赖
├── agent/
│ ├── search_agent.py # 主 Agent 类
│ └── prompts.py # Prompt 模板
├── models/
│ └── schemas.py # 数据模型
├── modules/ # 7 个功能模块
│ ├── query_analyzer.py
│ ├── search_planner.py
│ ├── search_executor.py
│ ├── content_extractor.py
│ ├── result_processor.py
│ ├── answer_generator.py
│ └── reflector.py
├── tools/ # 外部 API 封装
│ ├── serper.py
│ ├── jina_reader.py
│ └── jina_reranker.py
└── utils/
├── llm_client.py
└── helpers.py
目标结构(按模板)
search_agent/
├── Dockerfile # 新增:Docker 配置
├── README.md # 更新:按模板格式
├── requirements.txt # 更新:添加 pydantic-ai, mcp, fastmcp
├── run_api_server.py # 新增:统一启动脚本
└── src/
├── __init__.py # 新增
└── server/
├── __init__.py # 新增
├── api_server.py # 重构:按模板格式
└── mcp_server.py # 新增:MCP 工具定义
└── core/ # 新增:业务逻辑目录
├── __init__.py
├── config.py # 移动:配置管理
├── agent.py # 移动:SearchAgent 类
├── prompts.py # 移动:Prompt 模板
├── schemas.py # 移动:数据模型
├── modules/ # 移动:功能模块
│ ├── __init__.py
│ ├── query_analyzer.py
│ ├── search_planner.py
│ ├── search_executor.py
│ ├── content_extractor.py
│ ├── result_processor.py
│ ├── answer_generator.py
│ └── reflector.py
├── tools/ # 移动:外部 API 封装
│ ├── __init__.py
│ ├── serper.py
│ ├── jina_reader.py
│ └── jina_reranker.py
└── utils/ # 移动:工具函数
├── __init__.py
├── llm_client.py
└── helpers.py
主要变更点
1. 项目结构变更
| 变更类型 | 原路径 | 新路径 |
|---|---|---|
| 新增 | - | Dockerfile |
| 新增 | - | run_api_server.py |
| 新增 | - | src/__init__.py |
| 新增 | - | src/server/__init__.py |
| 重构 | api.py |
src/server/api_server.py |
| 新增 | - | src/server/mcp_server.py |
| 移动 | config.py |
src/server/core/config.py |
| 移动 | agent/search_agent.py |
src/server/core/agent.py |
| 移动 | agent/prompts.py |
src/server/core/prompts.py |
| 移动 | models/schemas.py |
src/server/core/schemas.py |
| 移动 | modules/* |
src/server/core/modules/* |
| 移动 | tools/* |
src/server/core/tools/* |
| 移动 | utils/* |
src/server/core/utils/* |
| 删除 | main.py |
- (功能合并到 api_server) |
| 删除 | .env |
- (环境变量由外部提供) |
| 删除 | 多个 README 文件 | 合并为一个 README.md |
2. 依赖变更
新增依赖(参考 agent_templates/requirements.txt):
pydantic-ai>=0.0.14
mcp>=0.9.0
fastmcp>=0.1.0
保留依赖:
aiohttp>=3.9.0
fastapi>=0.109.0
uvicorn[standard]>=0.27.0
pydantic>=2.5.0
python-dotenv>=1.0.0
loguru>=0.7.0
可移除依赖:
requests>=2.31.0 # 改用 aiohttp
orjson>=3.9.0 # 可选
typing-extensions>=4.9.0 # Python 3.12 内置
asyncio-throttle>=1.0.2 # 未使用
3. 配置变更
原配置方式(config.py):
- 使用
dataclass定义Config类 - 从
.env文件加载配置 - 包含 LLM、Serper、Jina 等多个 API 配置
新配置方式(按模板):
- 使用环境变量直接读取
- 主要配置项:
OPENAI_BASE_URL/LLM_BASE_URL:LiteLLM Gateway URLOPENAI_API_KEY:API Key(运行时传入)MODEL_NAME/LITELLM_MODEL:模型名称SERPER_API_KEY:Serper API KeyJINA_API_KEY:Jina API KeyAPI_PORT:服务端口
4. API 变更
原 API 端点:
GET /- 健康检查GET /health- 详细健康检查POST /search- 执行搜索GET /config- 获取配置
新 API 端点(按模板):
GET /- 服务状态GET /health- 健康检查POST /mcp- MCP HTTP 端点GET /mcp/sse- MCP SSE 端点POST /mcp/sse- MCP SSE POST 端点POST /api/v1/search- 业务 API(搜索)POST /api/v1/quick_search- 业务 API(快速搜索)
5. MCP 工具定义
新增 mcp_server.py,定义以下 MCP 工具:
@server.tool()
async def search(query: str, max_iterations: int = 3) -> str:
"""
执行智能搜索
Args:
query: 搜索查询问题
max_iterations: 最大迭代次数(1-10)
Returns:
JSON 格式的搜索结果
"""
@server.tool()
async def quick_search(query: str) -> str:
"""
快速搜索(单次迭代)
Args:
query: 搜索查询问题
Returns:
JSON 格式的搜索结果
"""
迁移任务清单
Phase 1: 创建基础结构
- 1.1 创建
src/目录结构 - 1.2 创建
src/__init__.py - 1.3 创建
src/server/__init__.py - 1.4 创建
src/server/core/目录结构
Phase 2: 迁移核心业务代码
-
2.1 迁移
config.py→src/server/core/config.py- 更新导入路径
- 适配新的环境变量命名
-
2.2 迁移
models/schemas.py→src/server/core/schemas.py- 更新导入路径
-
2.3 迁移
agent/prompts.py→src/server/core/prompts.py- 更新导入路径
-
2.4 迁移
utils/→src/server/core/utils/- 更新
llm_client.py导入路径 - 更新
helpers.py导入路径
- 更新
-
2.5 迁移
tools/→src/server/core/tools/- 更新
serper.py导入路径 - 更新
jina_reader.py导入路径 - 更新
jina_reranker.py导入路径
- 更新
-
2.6 迁移
modules/→src/server/core/modules/- 更新所有模块的导入路径
-
2.7 迁移
agent/search_agent.py→src/server/core/agent.py- 更新导入路径
Phase 3: 创建新的服务器文件
-
3.1 创建
src/server/mcp_server.py- 定义
search工具 - 定义
quick_search工具 - 导出
TOOL_MAP和TOOL_LIST
- 定义
-
3.2 创建
src/server/api_server.py- 按模板格式重构
- 添加 MCP 端点
- 添加业务 API 端点
- 移除原有的
LogCollector和AgentWrapper(简化)
Phase 4: 创建项目配置文件
-
4.1 创建
Dockerfile- 基于
agent_templates/Dockerfile
- 基于
-
4.2 创建
run_api_server.py- 基于
agent_templates/run_api_server.py
- 基于
-
4.3 更新
requirements.txt- 添加 pydantic-ai, mcp, fastmcp
- 移除不需要的依赖
-
4.4 更新
README.md- 按模板格式重写
Phase 5: 清理和测试
-
5.1 删除旧文件
api.pymain.pyconfig.py(根目录)agent/目录models/目录modules/目录tools/目录utils/目录- 多余的 README 文件
-
5.2 测试服务启动
python run_api_server.py
-
5.3 测试 API 端点
- 健康检查
- MCP 端点
- 搜索 API
关键代码变更示例
mcp_server.py 核心代码
"""Search Agent MCP 服务器"""
import os
import json
from typing import Optional
from mcp.server.fastmcp import FastMCP
from .core.config import Config
from .core.agent import SearchAgent
# 配置
_BASE_URL = os.getenv('OPENAI_BASE_URL',
os.getenv('LLM_BASE_URL', 'https://litellm.graystone-fb459c5d.southeastasia.azurecontainerapps.io/v1'))
_API_KEY = os.getenv('OPENAI_API_KEY', 'sk')
os.environ.setdefault('OPENAI_API_KEY', _API_KEY)
os.environ.setdefault('OPENAI_BASE_URL', _BASE_URL)
server = FastMCP('Search Agent')
SYSTEM_PROMPT = '''你是一个智能搜索助手。
能够理解用户查询意图、自动规划搜索策略、从多个来源获取信息,
并生成高质量、有来源引用的答案。'''
# 全局 Agent 实例
_agent: Optional[SearchAgent] = None
def get_agent() -> SearchAgent:
"""获取或创建 Agent 实例"""
global _agent
if _agent is None:
config = Config.from_env()
_agent = SearchAgent(config)
return _agent
@server.tool()
async def search(
query: str,
max_iterations: int = 3
) -> str:
"""
执行智能搜索
Args:
query: 搜索查询问题
max_iterations: 最大迭代次数(1-10)
Returns:
JSON 格式的搜索结果
"""
try:
agent = get_agent()
# 临时修改迭代次数
original = agent.config.max_iterations
agent.config.max_iterations = min(max(1, max_iterations), 10)
try:
response = await agent.search(query)
finally:
agent.config.max_iterations = original
return json.dumps({
"success": True,
"answer": response.answer.to_dict(),
"statistics": {
"iterations": response.iterations,
"total_sources_consulted": response.total_sources_consulted,
"search_queries_used": response.search_queries_used
}
}, ensure_ascii=False, indent=2)
except Exception as e:
return json.dumps({
"success": False,
"error": str(e)
}, ensure_ascii=False)
@server.tool()
async def quick_search(query: str) -> str:
"""
快速搜索(单次迭代)
Args:
query: 搜索查询问题
Returns:
JSON 格式的搜索结果
"""
try:
agent = get_agent()
answer = await agent.quick_search(query)
return json.dumps({
"success": True,
"answer": answer.to_dict()
}, ensure_ascii=False, indent=2)
except Exception as e:
return json.dumps({
"success": False,
"error": str(e)
}, ensure_ascii=False)
TOOL_MAP = {
'search': search,
'quick_search': quick_search,
}
TOOL_LIST = [
{
"name": "search",
"description": "执行智能搜索,支持多轮迭代和自我反思",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索查询问题"},
"max_iterations": {"type": "integer", "description": "最大迭代次数", "default": 3}
},
"required": ["query"]
}
},
{
"name": "quick_search",
"description": "快速搜索,单次迭代,适合简单问题",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "搜索查询问题"}
},
"required": ["query"]
}
}
]
导入路径变更对照表
| 原导入 | 新导入 |
|---|---|
from config import Config |
from .core.config import Config |
from models.schemas import ... |
from .core.schemas import ... |
from agent.search_agent import SearchAgent |
from .core.agent import SearchAgent |
from agent.prompts import ... |
from .core.prompts import ... |
from modules.xxx import ... |
from .core.modules.xxx import ... |
from tools.xxx import ... |
from .core.tools.xxx import ... |
from utils.xxx import ... |
from .core.utils.xxx import ... |
环境变量配置
迁移后需要设置的环境变量:
| 变量名 | 必需 | 说明 | 默认值 |
|---|---|---|---|
OPENAI_BASE_URL 或 LLM_BASE_URL |
是 | LiteLLM Gateway URL | - |
OPENAI_API_KEY |
是 | API Key(运行时传入) | sk |
MODEL_NAME 或 LITELLM_MODEL |
否 | 模型名称 | taiji/gpt-4o-mini |
SERPER_API_KEY |
是 | Serper API Key | - |
JINA_API_KEY |
是 | Jina API Key | - |
API_PORT |
否 | 服务端口 | 8000 |
MAX_ITERATIONS |
否 | 默认最大迭代次数 | 3 |
MAX_RESULTS_PER_QUERY |
否 | 每次搜索结果数 | 10 |
CONTENT_MAX_LENGTH |
否 | 内容最大长度 | 5000 |
LOG_LEVEL |
否 | 日志级别 | INFO |
TIMEOUT |
否 | 请求超时时间 | 30 |
注意事项
-
保持业务逻辑不变:迁移过程中不修改核心搜索逻辑,只调整项目结构和导入路径
-
API 兼容性:新增 MCP 端点的同时,保留原有的
/search端点(改为/api/v1/search) -
配置兼容:支持原有的环境变量命名,同时支持模板的命名方式
-
测试覆盖:迁移后需要测试所有功能模块是否正常工作
-
文档更新:合并多个 README 文件为一个统一的文档