Prompt / Planner / Executor / Tool / State 全部串起来,形成一个 L3(Planner + Sequential Executor) 的可落地参考实现
- 这个 Research Agent 要解决什么问题
- 架构与执行流程
- 项目目录
- 数据模型
- Prompt 文件
- Tool 定义
- Planner 实现
- Executor 实现
- Final Synthesizer
- Orchestrator 串联
- 一次完整请求的执行示例
- 可以直接抄的伪代码/简化代码
1. 场景定义
我们先定义一个具体任务。
用户请求
请分析 2024~2025 年 AI 编码工具市场趋势,并输出一份简短报告。
这是一个典型的 Research Agent 场景,因为它通常需要:
- 先搜索信息
- 再抽取关键事实
- 再做趋势分析
- 最后生成报告
这类任务:
- 不是单次问答
- 也不是高自由探索的纯 ReAct
- 非常适合 L3:Planner + Executor
2. 总体架构
这个示例采用:
- Task Interpreter(可选)
- Planner
- Sequential Executor
- Tool Registry
- State Store
- Final Synthesizer
整体链路如下:
text
用户请求
↓
任务理解(可选)
↓
Planner 生成线性计划
↓
Plan Validator
↓
Sequential Executor 顺序执行
├── Tool Step: web_search
├── LLM Step: extract_signals
└── LLM Step: write_report_draft
↓
Final Synthesizer
↓
最终回答
3. 目录结构
我们给这个 Research Agent 一个尽量小但完整的目录结构。
text
research_agent/
├── app/
│ ├── domain/
│ │ ├── models.py
│ │ └── enums.py
│ │
│ ├── prompts/
│ │ └── prompts_zh.py
│ │
│ ├── tools/
│ │ ├── base.py
│ │ ├── registry.py
│ │ └── web_search.py
│ │
│ ├── planner/
│ │ ├── planner.py
│ │ └── parser.py
│ │
│ ├── executor/
│ │ ├── sequential_executor.py
│ │ └── step_prompt_builder.py
│ │
│ ├── llm/
│ │ ├── base.py
│ │ └── mock_llm.py
│ │
│ ├── runtime/
│ │ ├── state.py
│ │ ├── orchestrator.py
│ │ └── synthesizer.py
│ │
│ └── main.py
└── README.md
4. 数据模型
我们先定义核心数据结构。
4.1 models.py
python
from pydantic import BaseModel, Field
from typing import List, Dict, Any, Optional
class TaskRequest(BaseModel):
user_id: str
query: str
session_id: Optional[str] = None
metadata: Dict[str, Any] = Field(default_factory=dict)
class PlanStep(BaseModel):
name: str
kind: str # tool | llm
tool_name: Optional[str] = None
instruction: Optional[str] = None
args: Dict[str, Any] = Field(default_factory=dict)
depends_on: List[str] = Field(default_factory=list)
class ExecutionPlan(BaseModel):
goal: str
steps: List[PlanStep]
class ToolResult(BaseModel):
success: bool
output: Any
error: Optional[str] = None
4.2 state.py
python
class AgentState:
def __init__(self):
self.values = {}
def set(self, key, value):
self.values[key] = value
def get(self, key, default=None):
return self.values.get(key, default)
def to_dict(self):
return self.values
5. Prompt 文件
这里直接给你一套中文版 prompt。
5.1 prompts_zh.py
python
SYSTEM_ZH_V1 = """
你是一个运行在受控软件系统中的 AI 组件。
请严格遵守以下规则:
1. 只能使用当前提供的上下文、工具结果和状态信息。
2. 不要编造事实、工具结果、执行结果或外部信息。
3. 如果信息不足,请明确说明不确定性。
4. 严格遵守要求的输出格式。
5. 除非提示中明确要求,否则不要自行执行动作或假设动作已完成。
"""
PLANNER_ZH_V1 = """
你是一个任务规划模块。
请将用户请求转换为一个可执行的线性步骤计划。
要求:
1. 计划尽量控制在 3 到 5 步之间。
2. 每一步只能是 tool 或 llm。
3. 你只负责规划,不执行。
4. 只能使用可用工具列表中的工具。
5. 只返回合法 JSON。
6. 如果任务是研究/分析/报告类任务,优先采用:
搜索 -> 提取关键信息 -> 生成报告
7. 不要创建重复搜索步骤。
可用工具列表:
{{tool_descriptions}}
输出格式:
{
"goal": "字符串",
"steps": [
{
"name": "步骤名称",
"kind": "tool | llm",
"tool_name": "工具名或 null",
"instruction": "步骤说明或 null",
"args": {},
"depends_on": []
}
]
}
用户请求:
{{user_input}}
"""
STEP_ANALYSIS_ZH_V1 = """
你是一个分析模块。
任务:
{{instruction}}
要求:
1. 只能使用提供的上下文。
2. 不要补充外部事实。
3. 输出简洁、结构化。
4. 区分"事实"和"分析判断"。
5. 如果证据不足,请明确说明不确定性。
上下文:
{{context}}
"""
REPORT_DRAFT_ZH_V1 = """
你是一个报告草稿生成模块。
请根据上下文,输出一份简洁专业的报告草稿。
结构要求:
1. 概要
2. 主要趋势
3. 关键玩家或产品动态
4. 不确定性 / 局限性
要求:
1. 只能使用上下文中的内容。
2. 不得编造事实。
3. 语言适合业务场景阅读。
4. 控制在 300~500 字左右。
上下文:
{{context}}
"""
FINAL_ANSWER_ZH_V1 = """
你是最终回答生成模块。
请基于执行状态,为用户生成最终答复。
要求:
1. 只能使用执行状态中的信息。
2. 不要编造事实。
3. 答案清晰、有条理。
4. 不暴露系统内部执行细节,除非用户明确要求。
5. 如果信息不足,请说明限制。
用户请求:
{{user_input}}
执行状态:
{{state}}
"""
FALLBACK_ZH_V1 = """
你是一个兜底回答生成模块。
当前系统未能完整完成任务。请基于已有信息给出尽可能有帮助的部分回答。
要求:
1. 说明当前限制。
2. 不要假装任务已完成。
3. 如合适,给出下一步建议。
用户请求:
{{user_input}}
当前可用状态:
{{state}}
执行问题:
{{issue}}
"""
6. Tool 定义
这个 Research Agent 最小只需要一个 web_search 工具。
6.1 base.py
python
from abc import ABC, abstractmethod
class BaseTool(ABC):
name: str
description: str
@abstractmethod
async def run(self, args, state):
...
6.2 registry.py
python
class ToolRegistry:
def __init__(self):
self._tools = {}
def register(self, tool):
self._tools[tool.name] = tool
def get(self, name):
if name not in self._tools:
raise ValueError(f"工具不存在: {name}")
return self._tools[name]
def describe_all(self):
lines = []
for name, tool in self._tools.items():
lines.append(f"- {name}: {tool.description}")
return "\n".join(lines)
6.3 web_search.py
这里用 mock 数据模拟搜索结果,方便你理解流程。
python
from app.tools.base import BaseTool
class WebSearchTool(BaseTool):
name = "web_search"
description = "用于搜索网页信息,适合市场、产品、公司、趋势相关研究"
async def run(self, args, state):
query = args["query"]
# 这里是 mock 数据;真实项目中接搜索 API
return {
"query": query,
"results": [
{
"title": "Cursor 持续增长,AI coding 工具市场加速",
"snippet": "2024 年以来,AI coding 工具成为开发者效率产品热点,Cursor、GitHub Copilot 等持续增长。"
},
{
"title": "GitHub Copilot 企业化推进",
"snippet": "GitHub Copilot 在企业开发场景中持续扩张,强调团队协作和代码质量。"
},
{
"title": "AI coding 市场竞争加剧",
"snippet": "新进入者围绕 agentic coding、IDE integration、团队协作能力展开竞争。"
},
{
"title": "资本关注开发者 AI 工具",
"snippet": "2024~2025 年,开发者 AI 工具持续受到资本市场关注,重点在生产力提升与企业采购。"
}
]
}
7. LLM 接口
为了演示清晰,我们用一个 BaseLLMClient,再给一个 mock 实现。
7.1 base.py
python
from abc import ABC, abstractmethod
class BaseLLMClient(ABC):
@abstractmethod
async def generate(self, prompt: str) -> str:
...
7.2 mock_llm.py
这个 mock 用于演示,不是真实模型行为。
python
import json
class MockLLMClient:
async def generate(self, prompt: str) -> str:
if "任务规划模块" in prompt:
plan = {
"goal": "分析 2024~2025 年 AI 编码工具市场趋势并生成简短报告",
"steps": [
{
"name": "search_market",
"kind": "tool",
"tool_name": "web_search",
"instruction": None,
"args": {"query": "2024 2025 AI 编码工具 市场趋势"},
"depends_on": []
},
{
"name": "extract_signals",
"kind": "llm",
"tool_name": None,
"instruction": "从搜索结果中提取市场趋势、主要玩家、融资和产品变化信号",
"args": {},
"depends_on": ["search_market"]
},
{
"name": "write_report_draft",
"kind": "llm",
"tool_name": None,
"instruction": "基于提取结果生成简短专业报告草稿",
"args": {},
"depends_on": ["extract_signals"]
}
]
}
return json.dumps(plan, ensure_ascii=False)
if "分析模块" in prompt:
return """
事实:
1. 2024~2025 年 AI 编码工具市场持续升温,开发者效率工具成为热点。
2. Cursor、GitHub Copilot 等是高频出现的代表性产品。
3. 市场竞争焦点集中在 IDE 集成、agentic coding、团队协作和企业化能力。
4. 资本市场持续关注开发者 AI 工具,企业采购潜力被反复提及。
分析判断:
1. 市场正在从"单点代码补全"向"更完整的开发工作流辅助"演进。
2. 企业场景将成为下一阶段的重要竞争点。
3. 新进入者仍有机会,但差异化将更多体现在产品体验和团队协作能力。
不确定性:
1. 当前信息主要来自搜索摘要,缺少一手财务和用户规模数据。
"""
if "报告草稿生成模块" in prompt:
return """
概要:
2024~2025 年,AI 编码工具市场保持快速发展,已从单纯代码补全逐步走向更完整的开发流程辅助。开发者效率提升与企业落地是市场的两大核心驱动力。
主要趋势:
一是头部产品持续强化 IDE 集成和开发工作流覆盖能力;二是市场竞争从基础能力比拼,逐渐转向 agentic coding、团队协作与企业级特性;三是企业场景的重要性明显上升,采购和部署潜力受到更多关注。
关键玩家或产品动态:
Cursor、GitHub Copilot 等是当前讨论度较高的代表产品。相关信息显示,这些产品都在强化面向实际开发流程的能力,而不仅仅局限于代码补全。
不确定性 / 局限性:
当前结论主要基于公开搜索结果摘要,缺少更系统的市场份额、收入和用户留存数据,因此更适合作为趋势性判断,而非严格定量结论。
"""
if "最终回答生成模块" in prompt:
return """
以下是对 2024~2025 年 AI 编码工具市场趋势的简要分析:
一、概要
AI 编码工具市场在 2024~2025 年持续升温,正在从"代码补全工具"逐步演化为"覆盖更完整开发流程的生产力工具"。市场关注点已不仅是生成代码本身,而是如何提升开发效率、协作效率与企业可落地性。
二、主要趋势
1. 市场从单点代码补全向更完整开发工作流辅助演进。
2. IDE 集成、agentic coding、团队协作成为竞争重点。
3. 企业级落地能力正在成为下一阶段的重要竞争因素。
4. 资本市场持续关注开发者 AI 工具,说明该方向仍具增长预期。
三、关键玩家
搜索结果中高频出现的代表产品包括 Cursor、GitHub Copilot 等。它们都在强化面向真实开发流程的能力,而不是仅提供基础补全功能。
四、局限性
以上结论主要基于公开搜索摘要,缺少一手市场规模、收入、用户留存等定量数据,因此更适合作为趋势判断,而非严格定量结论。
"""
return "无法处理该 prompt。"
8. Planner 实现
8.1 planner.py
python
import json
from app.domain.models import ExecutionPlan
from app.prompts.prompts_zh import PLANNER_ZH_V1
def render_prompt(template: str, **kwargs) -> str:
prompt = template
for k, v in kwargs.items():
prompt = prompt.replace("{{" + k + "}}", str(v))
return prompt
class ResearchPlanner:
def __init__(self, llm_client, tool_registry):
self.llm = llm_client
self.tool_registry = tool_registry
async def create_plan(self, task_request):
prompt = render_prompt(
PLANNER_ZH_V1,
user_input=task_request.query,
tool_descriptions=self.tool_registry.describe_all()
)
raw = await self.llm.generate(prompt)
plan_dict = json.loads(raw)
return ExecutionPlan(**plan_dict)
9. Executor 实现
Sequential Executor 会逐步执行:
- tool step
- llm step
- 更新 state
9.1 step_prompt_builder.py
python
from app.prompts.prompts_zh import STEP_ANALYSIS_ZH_V1, REPORT_DRAFT_ZH_V1
def render_prompt(template: str, **kwargs) -> str:
prompt = template
for k, v in kwargs.items():
prompt = prompt.replace("{{" + k + "}}", str(v))
return prompt
def build_llm_step_prompt(step, state):
if step.name == "extract_signals":
return render_prompt(
STEP_ANALYSIS_ZH_V1,
instruction=step.instruction,
context=state.get("search_market")
)
if step.name == "write_report_draft":
return render_prompt(
REPORT_DRAFT_ZH_V1,
context=state.get("extract_signals")
)
return render_prompt(
STEP_ANALYSIS_ZH_V1,
instruction=step.instruction or "请完成该步骤",
context=state.to_dict()
)
9.2 sequential_executor.py
python
from app.runtime.state import AgentState
from app.executor.step_prompt_builder import build_llm_step_prompt
class SequentialExecutor:
def __init__(self, tool_registry, llm_client):
self.tool_registry = tool_registry
self.llm = llm_client
async def execute(self, plan, state: AgentState):
for step in plan.steps:
if step.kind == "tool":
tool = self.tool_registry.get(step.tool_name)
result = await tool.run(step.args, state)
state.set(step.name, result)
elif step.kind == "llm":
prompt = build_llm_step_prompt(step, state)
result = await self.llm.generate(prompt)
state.set(step.name, result)
else:
raise ValueError(f"不支持的 step.kind: {step.kind}")
return state
10. Final Synthesizer
10.1 synthesizer.py
python
from app.prompts.prompts_zh import FINAL_ANSWER_ZH_V1
def render_prompt(template: str, **kwargs) -> str:
prompt = template
for k, v in kwargs.items():
prompt = prompt.replace("{{" + k + "}}", str(v))
return prompt
class FinalSynthesizer:
def __init__(self, llm_client):
self.llm = llm_client
async def generate(self, task_request, state):
prompt = render_prompt(
FINAL_ANSWER_ZH_V1,
user_input=task_request.query,
state=state.to_dict()
)
return await self.llm.generate(prompt)
11. Orchestrator 串联
11.1 orchestrator.py
python
from app.runtime.state import AgentState
class ResearchAgentOrchestrator:
def __init__(self, planner, executor, synthesizer):
self.planner = planner
self.executor = executor
self.synthesizer = synthesizer
async def run(self, task_request):
# 1. 规划
plan = await self.planner.create_plan(task_request)
# 2. 初始化状态
state = AgentState()
# 3. 执行
state = await self.executor.execute(plan, state)
# 4. 汇总最终回答
final_answer = await self.synthesizer.generate(task_request, state)
return {
"plan": plan.model_dump(),
"state": state.to_dict(),
"final_answer": final_answer
}
12. 主程序入口
12.1 main.py
python
import asyncio
from app.domain.models import TaskRequest
from app.tools.registry import ToolRegistry
from app.tools.web_search import WebSearchTool
from app.llm.mock_llm import MockLLMClient
from app.planner.planner import ResearchPlanner
from app.executor.sequential_executor import SequentialExecutor
from app.runtime.synthesizer import FinalSynthesizer
from app.runtime.orchestrator import ResearchAgentOrchestrator
async def main():
# 1. 初始化工具
tool_registry = ToolRegistry()
tool_registry.register(WebSearchTool())
# 2. 初始化 LLM
llm = MockLLMClient()
# 3. 初始化核心组件
planner = ResearchPlanner(llm, tool_registry)
executor = SequentialExecutor(tool_registry, llm)
synthesizer = FinalSynthesizer(llm)
orchestrator = ResearchAgentOrchestrator(
planner=planner,
executor=executor,
synthesizer=synthesizer
)
# 4. 构造请求
request = TaskRequest(
user_id="u_001",
query="请分析 2024~2025 年 AI 编码工具市场趋势,并输出一份简短报告"
)
# 5. 执行
result = await orchestrator.run(request)
print("=== PLAN ===")
print(result["plan"])
print("\n=== STATE ===")
print(result["state"])
print("\n=== FINAL ANSWER ===")
print(result["final_answer"])
if __name__ == "__main__":
asyncio.run(main())
13. 一次完整执行流程拆解
现在我们把整个执行过程串起来看一遍。
Step 1:用户输入
text
请分析 2024~2025 年 AI 编码工具市场趋势,并输出一份简短报告
Step 2:Planner 看到 Prompt
Planner 收到的是:
- 用户请求
- 可用工具描述
- 规划约束
模型输出:
json
{
"goal": "分析 2024~2025 年 AI 编码工具市场趋势并生成简短报告",
"steps": [
{
"name": "search_market",
"kind": "tool",
"tool_name": "web_search",
"args": {
"query": "2024 2025 AI 编码工具 市场趋势"
},
"depends_on": []
},
{
"name": "extract_signals",
"kind": "llm",
"instruction": "从搜索结果中提取市场趋势、主要玩家、融资和产品变化信号",
"depends_on": ["search_market"]
},
{
"name": "write_report_draft",
"kind": "llm",
"instruction": "基于提取结果生成简短专业报告草稿",
"depends_on": ["extract_signals"]
}
]
}
Step 3:Executor 执行 search_market
调用 web_search.run(...)
得到:
python
{
"query": "2024 2025 AI 编码工具 市场趋势",
"results": [
...
]
}
写入 state:
python
state["search_market"] = {...}
Step 4:Executor 执行 extract_signals
构造 Prompt:
- instruction = 从搜索结果中提取市场趋势、主要玩家、融资和产品变化信号
- context = state["search_market"]
LLM 输出:
text
事实:
1. ...
分析判断:
1. ...
不确定性:
1. ...
写入 state:
python
state["extract_signals"] = "..."
Step 5:Executor 执行 write_report_draft
构造 Prompt:
- context = state["extract_signals"]
LLM 输出报告草稿。
写入 state:
python
state["write_report_draft"] = "..."
Step 6:Final Synthesizer
输入:
- user_input
- 整个 state
生成最终面向用户的输出。
注意这里的好处:
- 规划与最终回答分离
- 中间 state 可追踪
- 每步都可调试
14. 这个示例体现了什么设计思想
这个 Research Agent 其实正好体现了前面所有关键结论。
14.1 它不是 L2 loop agent
它没有:
python
while not done:
think
act
observe
所以它避免了:
- 无限循环
- 步数不稳定
- 重复搜索
- 不可预测成本
14.2 它是 L3:Planner + Executor
控制权在系统,而不是 LLM 自己无限循环。
- LLM 负责规划
- 程序负责执行
- Tool 负责能力
- State 负责上下文传递
这就是现代生产系统更偏爱的形态。
14.3 Prompt 是模块化的
这里至少用了 4 类 Prompt:
PLANNER_ZH_V1STEP_ANALYSIS_ZH_V1REPORT_DRAFT_ZH_V1FINAL_ANSWER_ZH_V1
每个 Prompt 只负责一件事。
15. 如何升级到 L4
如果未来你想升级这个 Research Agent 到 L4,可以这样做。
15.1 当前 L3 的计划
text
search_market
↓
extract_signals
↓
write_report_draft
15.2 升级成 L4 的思路
如果你发现要同时搜索多个维度,比如:
- 搜趋势
- 搜代表产品
- 搜融资信息
- 搜企业落地案例
就可以改成 graph:
text
search_trends ─────┐
search_products ───┼──> extract_signals ──> write_report
search_funding ────┤
search_enterprise ─┘
这样:
- 多个搜索节点可并行
- 节点级重试更方便
- 节点级缓存更容易
15.3 代码层怎么预留
你现在就已经预留了 depends_on 字段,所以升级很自然:
- 把
SequentialExecutor替换成GraphExecutor - planner 改成
GraphPlanner - state 和 tool 基本可复用
16. 再给你一个更接近生产的增强建议
如果你要把这个示例变成真实可用版本,建议加上下面这些模块。
16.1 加 Plan Validator
校验:
- JSON 是否合法
- 是否用了未注册工具
- step 数是否超限
- depends_on 是否合理
16.2 加 Fallback
如果某一步失败:
- 返回部分结果
- 不直接报错给用户
16.3 加 Tracing
记录:
- request_id
- plan
- step latency
- token usage
- final answer
16.4 加 Cache
特别是:
- 搜索结果缓存
- 分析节点缓存
16.5 加 Critic
在最终输出前做一次轻量审查:
- 是否有 unsupported claims
- 是否遗漏明显事实
- 是否太长
17. 最终你可以怎样理解这套示例
你可以把这个 Research Agent 理解成:
- LLM 不是万能自治体
- LLM 是一个会规划、会分析、会写报告的组件
- 系统负责把这些能力组织成稳定 workflow
所以它不是:
"让一个大模型自由发挥"
而是:
"让大模型在受控框架下分工完成研究任务"
这就是今天大多数成熟 Agent 系统真正的工程方向。