Skip to content

Commit 184b448

Browse files
Feat: tRPC-Agent对齐Claude Code Plan Mod能力
1 parent 4336564 commit 184b448

30 files changed

Lines changed: 4798 additions & 7 deletions

docs/mkdocs/en/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Welcome to the English documentation for tRPC-Agent-Python.
1616
- [Memory](./memory.md): Store and retrieve long-term memories.
1717
- [Knowledge](./knowledge.md): Build RAG workflows with LangChain components.
1818
- [Multi-Agent](./multi_agents.md): Compose agents for complex workflows.
19+
- [Plan Mode](./plan.md): Design-then-implement workflow with write gate and HITL approval.
1920
- [Evaluation](./evaluation.md): Evaluate agent behavior and response quality.
2021

2122
For source code and examples, see the [GitHub repository](https://github.com/trpc-group/trpc-agent-python).

docs/mkdocs/en/plan.md

Lines changed: 343 additions & 0 deletions
Large diffs are not rendered by default.

docs/mkdocs/zh/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- [Memory](./memory.md):存储和检索长期记忆。
1717
- [Knowledge](./knowledge.md):基于 LangChain 组件构建 RAG 工作流。
1818
- [多 Agent](./multi_agents.md):编排多个 Agent 完成复杂任务。
19+
- [Plan Mode](./plan.md):先规划后实施,写工具 gate + HITL 审批。
1920
- [Evaluation](./evaluation.md):评测 Agent 行为和回复质量。
2021

2122
源码与示例请参考 [GitHub 仓库](https://github.com/trpc-group/trpc-agent-python)

docs/mkdocs/zh/plan.md

Lines changed: 343 additions & 0 deletions
Large diffs are not rendered by default.

examples/plan_mode/.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Set TRPC_AGENT_API_KEY、TRPC_AGENT_BASE_URL、TRPC_AGENT_MODEL_NAME
2+
TRPC_AGENT_API_KEY=your-api-key
3+
TRPC_AGENT_BASE_URL=your-base-url
4+
TRPC_AGENT_MODEL_NAME=your-model-name

examples/plan_mode/README.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Plan Mode 示例
2+
3+
演示 tRPC-Agent-Python 的 **Plan Mode**:用 `setup_plan()` 给 LLM Agent 装上「先规划、后实施」的工作流,并配合 `SpawnSubAgentTool`(Explore / Plan 两种子 agent 原型)完成代码探索与方案设计。
4+
5+
## 它解决什么问题
6+
7+
普通 coding agent 容易上来就改文件、边写边改方向。Plan Mode 把流程拆成两段:
8+
9+
1. **规划阶段** —— 模型只能用只读工具(Read / Grep / Glob)和子 agent(Explore / Plan)调研代码、撰写计划文档;写文件、改代码、执行命令等「副作用」工具被自动 gate,直到计划被人工批准。
10+
2. **实施阶段** —— 计划通过后,写工具自动解锁,模型按照批准的计划落地实现,并可用 `todo_write` 跟踪进度。
11+
12+
规划期间需要人工介入的三个动作——`enter_plan_mode` / `exit_plan_mode` / `ask_user_question`——都是 `LongRunningFunctionTool`,运行会暂停等待人类响应。这种交互用浏览器页面承载比 CLI 更自然,所以本示例同时提供了一个 AG-UI 服务端和一张零依赖的静态页面。
13+
14+
## 目录结构
15+
16+
```
17+
plan_mode/
18+
├── agent/
19+
│ ├── agent.py # orchestrator agent 定义:FileToolSet + SpawnSubAgentTool + TodoWriteTool + setup_plan
20+
│ ├── prompts.py # system instruction
21+
│ └── __init__.py
22+
├── static/
23+
│ └── index.html # AG-UI 浏览器 Demo(单文件,无构建依赖)
24+
├── run_agent_with_agui.py # FastAPI + AG-UI 服务端,托管 agent 接口与静态页
25+
├── .env # 模型配置(TRPC_AGENT_API_KEY / BASE_URL / MODEL_NAME)
26+
└── README.md
27+
```
28+
29+
## 架构
30+
31+
```
32+
orchestrator (LlmAgent + setup_plan)
33+
├── FileToolSet # Read/Grep/Glob 始终可用;Write/Edit/Bash 在计划批准后解锁
34+
├── SpawnSubAgentTool(EXPLORE_AGENT, PLAN_AGENT) # 只读调研与方案设计子 agent
35+
├── TodoWriteTool # 实施阶段用于跟踪进度
36+
└── PlanToolSet # enter_plan_mode / update_plan_content / exit_plan_mode / ask_user_question
37+
```
38+
39+
- 计划文档持久化在**主 agent 的 session** 中(`state["plan"]`)。
40+
- 被 spawn 出来的子 agent 只返回文本,不直接改动主 agent 的状态。
41+
42+
## 前置条件
43+
44+
```bash
45+
# 1. 安装 SDK(含 AG-UI 依赖)
46+
git clone https://github.com/trpc-group/trpc-agent-python.git
47+
cd trpc-agent-python
48+
python3 -m venv .venv
49+
source .venv/bin/activate
50+
pip3 install -e .
51+
52+
# 2. 配置模型访问
53+
# 复制并填写 examples/plan_mode/.env:
54+
TRPC_AGENT_API_KEY=<你的 key>
55+
TRPC_AGENT_BASE_URL=<可选,自定义 endpoint>
56+
TRPC_AGENT_MODEL_NAME=<可选,默认 gpt-4.1-mini>
57+
```
58+
59+
## 运行
60+
61+
```bash
62+
cd examples/plan_mode
63+
64+
# 完整 agent + 基于 AG-UI 的浏览器页面(推荐)
65+
python3 run_agent_with_agui.py
66+
67+
# 自定义监听地址 / 端口
68+
python3 run_agent_with_agui.py --host 0.0.0.0 --port 8080
69+
```
70+
71+
启动后控制台会打印三个地址:
72+
73+
```
74+
Plan Mode AG-UI demo: http://127.0.0.1:18090/
75+
Agent endpoint: http://127.0.0.1:18090/plan_agent
76+
Health check: http://127.0.0.1:18090/health
77+
```
78+
79+
> 未设置 `TRPC_AGENT_API_KEY` 时服务仍能启动,但会打印警告,agent 调用会失败。
80+
81+
## 浏览器 Demo 说明
82+
83+
打开 <http://127.0.0.1:18090/> ——静态页面和 agent 接口(`/plan_agent`)由同一个 FastAPI 应用提供,因此页面可同源调用接口,无需任何 CORS 配置。
84+
85+
页面提供:
86+
87+
- **聊天面板** —— 与 orchestrator 对话。
88+
- **实时计划面板** —— 根据 AG-UI 每次运行结束发送的 `STATE_SNAPSHOT` 事件更新(解析 `plan:orchestrator` 这个 session state key,见 `trpc_agent_sdk/plan_mode/_helpers.py:state_key`)。
89+
90+
三种 HITL 交互的页面表现:
91+
92+
| 模型动作 | 页面表现 | 用户操作后续跑格式 |
93+
|---|---|---|
94+
| `enter_plan_mode` | 展示「确认进入 Plan Mode」卡片 | 确认后进入 `exploring` 状态,开启写工具 gate |
95+
| `exit_plan_mode` | 展示「通过 / 拒绝计划」卡片 | `{"role":"tool","toolCallId":...,"content":"{\"status\":\"approved\",...}"}` |
96+
| `ask_user_question` | 展示问题与选项卡片 | 同上;问题文本/选项/`question_id` 取自状态快照中的 `askedQuestions` |
97+
98+
> `exit_plan_mode` 触发时本次运行会在没有 `TOOL_CALL_RESULT` 的情况下结束——这是 AG-UI 表示「长时间运行的工具调用被暂停」的方式。点击按钮续跑的报文格式,与任何宿主应用需满足的 `process_hitl_function_response` 期望格式一致。
99+
100+
静态页 `static/index.html` 是一个**无任何依赖的单文件**(无需构建、无需 npm install),可直接在浏览器打开。如需用 Node 驱动同一接口(例如写测试脚本),参考 [examples/agui/client_js](../agui/client_js) 中的 `@ag-ui/client` 写法。
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# trpc-agent-python is licensed under the Apache License Version 2.0.
6+
7+
from .agent import create_plan_agent
8+
9+
__all__ = ["create_plan_agent"]

examples/plan_mode/agent/agent.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# trpc-agent-python is licensed under the Apache License Version 2.0.
6+
7+
from __future__ import annotations
8+
9+
import os
10+
11+
from dotenv import load_dotenv
12+
from trpc_agent_sdk.agents import LlmAgent
13+
from trpc_agent_sdk.agents.sub_agent import EXPLORE_AGENT
14+
from trpc_agent_sdk.agents.sub_agent import PLAN_AGENT
15+
from trpc_agent_sdk.models import OpenAIModel
16+
from trpc_agent_sdk.plan_mode import setup_plan
17+
from trpc_agent_sdk.tools import FileToolSet
18+
from trpc_agent_sdk.tools import SpawnSubAgentTool
19+
from trpc_agent_sdk.tools import TodoWriteTool
20+
21+
from .prompts import SYSTEM_INSTRUCTION
22+
23+
load_dotenv()
24+
25+
26+
def create_plan_agent() -> LlmAgent:
27+
model = OpenAIModel(
28+
model_name=os.environ.get("TRPC_AGENT_MODEL_NAME", "gpt-4.1-mini"),
29+
api_key=os.environ.get("TRPC_AGENT_API_KEY", ""),
30+
base_url=os.environ.get("TRPC_AGENT_BASE_URL"),
31+
)
32+
agent = LlmAgent(
33+
name="orchestrator",
34+
model=model,
35+
instruction=SYSTEM_INSTRUCTION,
36+
tools=[
37+
# Full file toolset: write/edit/bash are gated by setup_plan()
38+
# while a plan is active and unlock automatically on approval.
39+
FileToolSet(),
40+
SpawnSubAgentTool(agents=[EXPLORE_AGENT, PLAN_AGENT]),
41+
# todo_write is gated during plan mode; use after approval to track implementation.
42+
TodoWriteTool(),
43+
],
44+
)
45+
setup_plan(agent)
46+
return agent
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# trpc-agent-python is licensed under the Apache License Version 2.0.
6+
7+
SYSTEM_INSTRUCTION = """\
8+
You are a coding assistant. Prefer reusing existing code and patterns in the workspace."""
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
2+
#
3+
# Copyright (C) 2026 Tencent. All rights reserved.
4+
#
5+
# trpc-agent-python is licensed under the Apache License Version 2.0.
6+
"""AG-UI server for the Plan Mode example.
7+
8+
Exposes the plan_mode orchestrator (see agent/agent.py) over the AG-UI
9+
protocol and serves a small, dependency-free static HTML page
10+
(static/index.html) from the same FastAPI app so it can call the agent
11+
endpoint same-origin (no CORS setup needed).
12+
13+
The page lets a human watch the plan document update live and
14+
approve / reject / answer questions from the browser instead of
15+
simulating the decision in Python.
16+
17+
Run:
18+
cd examples/plan_mode
19+
python3 run_agent_with_agui.py
20+
21+
Then open http://127.0.0.1:18090/ in a browser.
22+
23+
Prerequisites:
24+
pip install -e '.[ag-ui]'
25+
Set TRPC_AGENT_API_KEY (and optionally TRPC_AGENT_BASE_URL / TRPC_AGENT_MODEL_NAME)
26+
in examples/plan_mode/.env
27+
"""
28+
29+
from __future__ import annotations
30+
31+
import argparse
32+
import os
33+
import sys
34+
from contextlib import asynccontextmanager
35+
from pathlib import Path
36+
37+
from dotenv import load_dotenv
38+
from fastapi import FastAPI
39+
from fastapi.responses import FileResponse
40+
from pydantic import BaseModel
41+
42+
from trpc_agent_sdk.log import logger
43+
from trpc_agent_sdk.server.ag_ui import AgUiAgent
44+
from trpc_agent_sdk.server.ag_ui import AgUiManager
45+
from trpc_agent_sdk.server.ag_ui import AgUiService
46+
47+
_EXAMPLE_DIR = Path(__file__).resolve().parent
48+
if str(_EXAMPLE_DIR) not in sys.path:
49+
sys.path.insert(0, str(_EXAMPLE_DIR))
50+
51+
load_dotenv(_EXAMPLE_DIR / ".env")
52+
53+
HOST = "127.0.0.1"
54+
PORT = 18090
55+
APP_NAME = "plan_mode_agui_demo"
56+
AGENT_URI = "/plan_agent"
57+
STATIC_DIR = _EXAMPLE_DIR / "static"
58+
INDEX_HTML = STATIC_DIR / "index.html"
59+
60+
agui_manager = AgUiManager()
61+
62+
63+
class HealthResponse(BaseModel):
64+
"""Response body for GET /health."""
65+
66+
status: str = "ok"
67+
app_name: str
68+
agent_uri: str
69+
70+
71+
def _create_agui_agent() -> AgUiAgent:
72+
"""Lazy factory so each worker process gets its own agent instance."""
73+
from agent.agent import create_plan_agent
74+
75+
return AgUiAgent(trpc_agent=create_plan_agent(), app_name=APP_NAME)
76+
77+
78+
@asynccontextmanager
79+
async def _lifespan(app: FastAPI): # noqa: ARG001
80+
if not os.environ.get("TRPC_AGENT_API_KEY"):
81+
logger.warning(
82+
"TRPC_AGENT_API_KEY is not set — copy .env.example values into %s/.env",
83+
_EXAMPLE_DIR,
84+
)
85+
if not INDEX_HTML.is_file():
86+
raise FileNotFoundError(f"Demo page not found: {INDEX_HTML}")
87+
logger.info("Plan Mode AG-UI demo starting up.")
88+
yield
89+
logger.info("Plan Mode AG-UI demo shutting down.")
90+
await agui_manager.close()
91+
92+
93+
def create_app() -> FastAPI:
94+
"""Build the FastAPI app: AG-UI agent endpoint + static demo page."""
95+
app = FastAPI(title="Plan Mode AG-UI Demo", lifespan=_lifespan)
96+
97+
@app.get("/", response_class=FileResponse)
98+
async def index() -> FileResponse:
99+
return FileResponse(INDEX_HTML)
100+
101+
@app.get("/health", response_model=HealthResponse)
102+
async def health() -> HealthResponse:
103+
return HealthResponse(app_name=APP_NAME, agent_uri=AGENT_URI)
104+
105+
service = AgUiService(APP_NAME, app=app)
106+
service.add_agent(AGENT_URI, _create_agui_agent)
107+
agui_manager.register_service(APP_NAME, service)
108+
agui_manager.set_app(app)
109+
return app
110+
111+
112+
app = create_app()
113+
114+
115+
def serve(host: str = HOST, port: int = PORT) -> None:
116+
"""Start the FastAPI + AG-UI server."""
117+
print(f"Plan Mode AG-UI demo: http://{host}:{port}/")
118+
print(f"Agent endpoint: http://{host}:{port}{AGENT_URI}")
119+
print(f"Health check: http://{host}:{port}/health")
120+
agui_manager.run(host, port)
121+
122+
123+
def _parse_args() -> argparse.Namespace:
124+
parser = argparse.ArgumentParser(description="Plan Mode AG-UI browser demo")
125+
parser.add_argument("--host", default=HOST, help=f"bind address (default: {HOST})")
126+
parser.add_argument("--port", type=int, default=PORT, help=f"listen port (default: {PORT})")
127+
return parser.parse_args()
128+
129+
130+
if __name__ == "__main__":
131+
args = _parse_args()
132+
serve(host=args.host, port=args.port)

0 commit comments

Comments
 (0)