# project1 **Repository Path**: sunineo/project1 ## Basic Information - **Project Name**: project1 - **Description**: 手写 ReAct 智能体:不依赖 LangChain 与模型原生 function calling,自主实现 Thought/Action/Observation 循环、工具注册与重试,含分层测试 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-03 - **Last Updated**: 2026-10-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 手写原生 ReAct 单智能体 基于纯文本解析的 ReAct(Reasoning + Acting)单智能体,不使用 LangChain AgentExecutor、不使用模型原生 function calling。 ## 技术栈 - Python 3.12+(开发环境为 3.13) - 通义千问 API(OpenAI 兼容接口) - SerpAPI(联网搜索) - 测试框架:pytest(单元测试以标准库 unittest 风格编写,集成/端到端测试使用 pytest fixture) ## 模块结构 | 模块 | 职责 | |------|------| | `config.py` | 配置项集中管理(密钥、模型参数、迭代上限) | | `llm_client.py` | 封装通义千问 API,仅返回文本 | | `utils/parser.py` | 正则解析 LLM 输出(Thought/Action/Action Input/Final Answer) | | `tools/base_tool.py` | 工具抽象基类 | | `tools/calc_tool.py` | 计算器(ast 白名单安全求值,不用 eval) | | `tools/search_tool.py` | SerpAPI 联网搜索 | | `tools/tool_manager.py` | 工具注册与按名调度 | | `react_agent.py` | ReAct 主循环(scratchpad、重试、防死循环) | | `main.py` | 命令行入口 | | `tests/` | 单元测试(各 `test_*.py`) | | `tests/integration/` | 离线集成测试(真实组件拼装,仅 LLM/搜索边界打桩) | | `tests/live/` | 真实 API 端到端冒烟测试(默认跳过,需手动开启) | ## 安装 ```bash python -m venv venv # Windows (PowerShell): venv\Scripts\Activate.ps1 source venv/bin/activate pip install -r requirements.txt pip install -r requirements-dev.txt # 运行测试需要(pytest) ``` ## 配置 复制 `.env.example` 为 `.env`,填入密钥: ```bash cp .env.example .env ``` 必填: - `DASHSCOPE_API_KEY`:通义千问 API 密钥 使用搜索工具时必填: - `SERPAPI_API_KEY`:SerpAPI 密钥 其余项有默认值,按需修改。 > `.env` 已写入 `.gitignore`,不会被提交;请勿把密钥写入代码或命令行参数。 ## 运行 单次问答模式: ```bash python main.py "2024 年奥运会在哪个城市举办?" ``` 交互式循环模式(输入 `quit` 或空行退出): ```bash python main.py ``` 推理轨迹(Thought / Action / Observation / Final Answer)实时输出到 **stderr**,最终答案输出到 **stdout**,互不干扰。用 `LOG_LEVEL` 控制详细程度: ```bash LOG_LEVEL=DEBUG python main.py "问题" # DEBUG:额外打印 LLM 原始输出 LOG_LEVEL=WARNING python main.py "问题" # 静默:只看最终答案 ``` ## 运行测试 测试分三层,统一通过 pytest 运行: | 层级 | 目录 | 说明 | 是否访问外网 | |------|------|------|--------------| | 单元测试 | `tests/test_*.py` | 单个模块,外部依赖全部 mock | 否 | | 集成测试 | `tests/integration/` | 真实 agent/parser/工具拼装,仅 LLM、SerpAPI 边界打桩 | 否 | | 端到端测试 | `tests/live/` | 真实调用通义千问 / SerpAPI 的冒烟测试 | 是,有费用 | 运行全部**离线**测试(单元 + 集成,端到端自动跳过): ```bash python -m pytest -v ``` 只跑集成测试: ```bash python -m pytest tests/integration -v ``` 端到端测试默认跳过,需同时满足「`.env` 已配置密钥」和「设置 `RUN_LIVE_TESTS=1`」才会真实发请求: ```bash # macOS / Linux RUN_LIVE_TESTS=1 python -m pytest tests/live -v # Windows PowerShell $env:RUN_LIVE_TESTS="1" python -m pytest tests/live -v ``` 其中纯 LLM 用例只需 `DASHSCOPE_API_KEY`;搜索全链路用例还需 `SERPAPI_API_KEY`,密钥缺失时对应用例自动 skip。断言只验证链路成功(可解析、有 Observation、收敛到 Final Answer),不断言具体答案内容。 ## 工作原理 ReAct 循环每轮: 1. 拼装 prompt(系统指令 + 工具说明 + 问题 + 历史 scratchpad) 2. 调 LLM 返回文本 3. 正则解析提取 Thought / Action / Action Input / Final Answer 4. 若为 Final Answer 则结束返回;否则执行工具得到 Observation,按固定格式追加到 scratchpad,进入下一轮 安全与健壮性: - 计算器用 ast 白名单求值,拒绝函数调用/属性访问/名称,杜绝代码注入 - 解析失败最多重试 `PARSE_RETRY_LIMIT` 次(默认 2) - 最大迭代 `MAX_ITERATIONS`(默认 10)防止死循环 - 所有密钥经环境变量读取,不硬编码