这是一个可独立运行、可直接封装为 PAI-LangStudio 代码模式应用的纯 Python Research Agent 运行时,用于参加天池赛题“用 PAI-LangStudio 实现 Research Agent”。
目标不是做一个通用聊天机器人,而是做一个面向 100 道多跳事实题的搜索型 Agent:
- 使用
Qwen系列模型完成规划、工具调用和答案归一化 - 使用
websearch和webfetch两个工具做联网检索 - 通过
HTTP + SSE暴露标准接口,供PAI-EAS评测脚本调用 - 在
10 分钟单题时限内返回纯文本答案
flowchart TD
A[HTTP POST / question] --> B[agent.py]
B --> C[agent_loop.py]
C --> D[task_subagent_python.run_top_level_agent]
D --> E[llm_fallback_python]
D --> F[task_subagent_python]
F --> G[websearch_python]
F --> H[webfetch_python]
G --> I[Serper / BrightData / IQS]
H --> J[Direct Fetch / Proxy / IQS Readpage]
E --> K[DashScope Coding Endpoint]
C --> L[SSE Message / Ping]
主链说明:
agent.py- 对外 HTTP 入口
- 负责普通 JSON 响应和
text/event-stream响应 - 按比赛要求输出
event: Ping和event: Message
agent_loop.py- 模板层 agent loop
- 把底层 trace 解析成
tool_call / tool_call_result / text
task_subagent_python- 负责任务执行、subagent、工具批量调用、最终答案收敛
llm_fallback_python- 负责模型调用、fallback、冷却、请求/响应日志
websearch_python- 负责 provider routing、搜索、候选打分、缓存
webfetch_python- 负责页面抓取、fallback、窗口化输出、缓存
这个运行时遵循下面的工程策略:
LLM只做规划、调用工具、整合证据和输出答案。- 搜索和抓取逻辑都放在工具层,不依赖模型自带搜索。
- 模型侧统一走
llm_fallback_python,把:- 模型链
- fallback
- cooldown
- payload 级日志 收口到一层。
task/subagent逻辑独立于 HTTP 层,既能被 HTTP 调,也能被 CLI 和批跑脚本调用。- 输出阶段强约束为“只返回最终答案”,避免多余解释影响比赛判分。
llm_basic_xvNU/
├── agent.py
├── agent_loop.py
├── agui.py
├── runtime_cli.py
├── runtime.env
├── runtime_env.py
├── trace_utils.py
├── skills.py
├── skills/
├── scripts/
│ ├── tianchi_ab_benchmark.py
│ └── forced_task_chain_ab.py
├── llm_fallback_python/
├── task_subagent_python/
├── websearch_python/
└── webfetch_python/
核心模块:
agent.pyFastAPI appPOST /POST /ag-ui
runtime_cli.pytoptaskwebsearchwebfetch
scripts/tianchi_ab_benchmark.py- 基于题库和答案库做本地对比回归
runtime_env.py- 自动读取
runtime.env/.env/.env.local
- 自动读取
建议使用 Python 3.10+。
cd /path/to/llm_basic_xvNU
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install --upgrade pip最少需要这些包:
pip install fastapi uvicorn requests pydantic PyYAML beautifulsoup4 lxml如果要在 LangStudio / EAS 上部署,建议固定版本后自行整理 requirements.txt,至少覆盖:
fastapiuvicornrequestspydanticPyYAMLbeautifulsoup4lxml
运行时通过 runtime_env.py 自动加载环境变量,顺序大致为:
OPENSEARCH_ENV_FILE指定的文件- 当前运行时目录下的
.env / .env.local / runtime.env / env.local - 当前工作目录下的同名文件
- 运行时上一级目录下的同名文件
export DASHSCOPE_API_KEY="your_dashscope_key"
export OPENSEARCH_PYTHON_DASHSCOPE_BASE_URL="https://coding.dashscope.aliyuncs.com/v1"export OPENSEARCH_PYTHON_MODEL="qwen3.5-plus"
export OPENSEARCH_PYTHON_MODEL_CHAIN="qwen3.5-plus,qwen3-max-2026-01-23,qwen3-coder-next,qwen3-coder-plus"
export OPENSEARCH_PYTHON_FALLBACK_COOLDOWN_MS="60000"
export OPENSEARCH_PYTHON_MAX_SECONDS="600"
export OPENSEARCH_PYTHON_MAX_STEPS="256"
export OPENSEARCH_SERPER_API_KEY="your_serper_key"
export OPENSEARCH_BRIGHTDATA_API_TOKEN="your_brightdata_token"
export OPENSEARCH_IQS_AUTHORIZATION="Bearer your_iqs_token"
export OPENSEARCH_IQS_LITE_URL="https://iqs-mcp.aliyuncs.com/mcp-servers/iqs-mcp-server-litesearch"
export OPENSEARCH_IQS_SEARCH_URL="https://iqs-mcp.aliyuncs.com/mcp-servers/iqs-mcp-server-search"
export OPENSEARCH_IQS_READPAGE_URL="https://iqs-mcp.aliyuncs.com/mcp-servers/iqs-mcp-server-readpage"
export OPENSEARCH_HTTP_PROXY_URL="http://user:pass@host:port/"说明:
- 默认模型链是“从强到弱 fallback”。
runtime.env可以直接被 Python 运行时读取。- 其中
OPENSEARCH_IQS_AUTHORIZATION=Bearer ...如果不加引号,不能直接被 shellsource,但运行时代码可以正常解析。
顶层 agent:
python3 runtime_cli.py top "伍佰 本名是什么"单个 task:
python3 runtime_cli.py task --description "Lookup singer alias" "伍佰 本名是什么"直接测搜索:
python3 runtime_cli.py websearch "伍佰 本名"直接测抓取:
python3 runtime_cli.py webfetch "https://example.com"带 trace:
python3 runtime_cli.py top --trace-path ./logs/q1.trace.jsonl "伍佰 本名是什么"uvicorn agent:app --host 0.0.0.0 --port 8000普通 JSON 调用:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"question":"伍佰 本名是什么?"}' \
http://127.0.0.1:8000/比赛要求在长请求中使用 text/event-stream,并在最终答案前定期发送 Ping,防止网关 180s 无数据断开。
当前实现位置:
行为:
- 检测请求头
Accept: text/event-stream - 周期性发送:
event: Ping
- 最终答案按多段发送:
event: Message
data: {"answer": "..."}
本地 SSE 调用示例:
curl -N -X POST \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"question":"hi"}' \
http://127.0.0.1:8000/返回示例:
event: Ping
event: Ping
event: Message
data: {"answer": "Hello! How can I help "}
event: Message
data: {"answer": "you today?"}
比赛第二阶段需要提交一个 service.json,格式如下:
{
"api_url": "https://your-public-eas-endpoint",
"auth_token": "your_eas_token"
}评测脚本会按以下方式调用:
curl -X POST \
-H "Authorization: Bearer <your_token>" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{"question":"Where is the capital of France?"}' \
"<your_endpoint>"- 新建 LangStudio 应用,选择代码模式。
- 将本目录代码作为项目内容导入。
- 确保入口暴露为
agent.py中的全局app。 - 在环境变量里配置:
DASHSCOPE_API_KEYOPENSEARCH_PYTHON_DASHSCOPE_BASE_URL- 搜索 provider 所需的 key/token/url
- 本地或 LangStudio 内先验证:
POST /Accept: text/event-stream- 复杂题目能在 10 分钟内收敛
- 在 LangStudio 中部署应用到 EAS。
- 获取:
- 公网
Endpoint Token
- 公网
- 生成提交文件:
{
"api_url": "your_public_api_url",
"auth_token": "your_auth_token"
}至少自测这几项:
- 非流式
POST /返回{"answer": "..."} - 流式
POST /返回Ping + Message - 单题耗时不超过
10 分钟 - 答案只有纯文本,不带解释、前缀、XML、Markdown
python3 scripts/tianchi_ab_benchmark.py --question-id 80 --repo new --answer-bank high-confidencepython3 scripts/tianchi_ab_benchmark.py --answer-bank high-confidence --limit 10 --repo newpython3 scripts/tianchi_ab_benchmark.py --answer-bank high-confidence --random --limit 1 --repo newpython3 scripts/forced_task_chain_ab.py说明:
- 当前脚本同时支持新老仓对比逻辑,但用于提交时,重点是验证本 Python runtime 自己能稳定跑题。
- 运行结果、stdout、stderr、trace 会落到对应 trace 目录。
task/subagent/websearch/webfetch 的链路 trace 可以通过 --trace-path 输出为 JSONL。
典型事件包括:
agent_startllm_requestllm_responsetool_calltool_resulttask_starttask_finishagent_finish
llm_fallback_python 会额外输出更细的日志,包括:
- 总体链路日志
- 每次 payload 请求
- 每次 payload 响应
- fallback / cooldown 事件
适合排查:
- 模型是否 fallback
- 哪个模型超时
- 是否被 quota / rate limit 限流
- 这是面向天池赛题的搜索型 Agent,不是通用聊天助手。
- 顶层 prompt 和 skill 仍带有明显的“事实题/搜索题”偏向。
- 某些题仍可能因为搜索源漂移、抓取阻断或工具质量波动而失败。
runtime.env中包含示例/本地配置;正式提交前应替换成部署环境变量,不要把敏感 token 直接打包进交付物。
POST /可用Accept: text/event-stream可用event: Ping正常发送event: Message的data.answer正确service.json已准备- 题库批跑脚本已自测
- README 已包含依赖、部署、调用示例、复现步骤
- 只输出最终答案,不输出解释
- 单题耗时满足
<= 10 min