Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
358 changes: 358 additions & 0 deletions docs/mkdocs/en/tool.md

Large diffs are not rendered by default.

161 changes: 161 additions & 0 deletions docs/mkdocs/zh/tool.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Agent 通过以下步骤动态使用工具:
| [WebSearchTool](#websearchtool) | 公网搜索引擎检索 | 实例化 WebSearchTool 并加入 tools | 实时资讯、版本发布、事实/定义查询 |
| [TodoWriteTool](#todowritetool-任务清单工具) | 多步任务规划与进度跟踪(整表替换) | 挂载 `TodoWriteTool` | 短清单、无依赖编排、token 不敏感 |
| [Task 工具族](#task-工具族结构化任务看板) | 结构化任务看板(按 id 增量更新 + 依赖) | 挂载 `TaskToolSet` | 长任务板、跨轮跟踪、blockedBy 依赖 |
| [Goal 工具族](#goal-工具族持久会话目标) | 单会话持久目标 + 强制收尾 | `setup_goal(agent)` | 跨轮大目标、宿主设目标、未完成不许收尾 |
| [Agent Code Executor](./code_executor.md) | 自动生成并执行代码场景、数据处理场景 | 配置 CodeExecutor | API 自动调用、表格数据处理 |
---

Expand Down Expand Up @@ -3040,3 +3041,163 @@ print(render_task_list(store))
| --- | --- |
| [examples/task_tools](../../../examples/task_tools/) | 多轮对话:依赖编排、逐项完成、跨轮 `get_task_store` 读回看板 |
| [examples/task_tools_parallel](../../../examples/task_tools_parallel/) | 验证 `parallel_tool_calls` 与 `task_store_lock`(Phase 1–2 无需 API Key) |

---

## Goal 工具族(持久会话目标)

`GoalToolSet` 暴露三个工具——`create_goal`、`get_goal`、`update_goal`——对齐 Claude Code 的 **Session Goal** 能力。与 `TodoWriteTool`(多行待办)和 `TaskToolSet`(多任务看板)不同,Goal 在每个 session branch 上**至多只有一个**持久目标:在目标为 `active` 期间,模型给出「看起来像最终答案」的文本**不算完成**——必须继续执行,或显式调用 `update_goal('complete' | 'blocked')` 收尾。

目标序列化为**单个 JSON blob**(`GoalRecord`)写入 `tool_context.state["goal[:<branch>]"]`,跨 `Runner.run_async` 调用存活。除三个模型工具外,完整能力还需通过 `setup_goal()` 挂载一对 **enforcement callbacks**(`before_model` / `after_model`),在**同一次 invocation 内**拦截过早的最终回复并自动重试。

### 功能特性

- **单目标契约**:每个 branch 一个 `GoalRecord`(`objective` + 三态 `active` / `complete` / `blocked`);`complete` / `blocked` 为**不可逆**终态
- **跨轮持久化**:随 function-response 的 state delta 落库;**勿用** `temp:` 前缀
- **子 Agent 隔离**:state key 追加 `:<branch>`,父 / 子 Agent 各自维护独立目标
- **强制收尾(enforcement)**:目标 `active` 时,`after_model` 检测「无 tool call、有可见文本、非 partial」的过早 final,抑制该回复并在同 invocation 内 re-run;`before_model` 注入 user-role nudge
- **fail-open 预算**:`max_retries`(默认 3)次拦截后放行最终回复,避免无限循环;计数器存在 invocation 级 `agent_context.metadata`,不持久化
- **双入口创建**:
- **模型侧**:`create_goal(objective=...)` —— LLM 判断多步任务后自主创建
- **宿主侧**:`start_goal(session_service, ...)` —— 应用层在首轮前写入 session,模型无需调用 `create_goal`
- **Prompt 引导分层**:`DEFAULT_GUIDANCE` 在目标 active 时经 `before_model` 注入 system instruction(`inject_guidance=True`);硬约束由 store 校验 + callback 共同保证
- **并发安全**:`_GoalToolBase` 在 load → mutate → save 外包 `goal_store_lock`(按 session + branch),兼容 `parallel_tool_calls=True`

### 与 Todo / Task 的关系

| 维度 | `TodoWriteTool` | `TaskToolSet` | Goal 工具族 |
| --- | --- | --- | --- |
| 粒度 | 多行待办清单 | 多任务看板 + 依赖 | **单个**会话目标 |
| 更新方式 | 整表替换 | 按 `taskId` 增量 | `create_goal` / `update_goal` |
| 未完成能否收尾 | Prompt 引导 | Prompt 引导 | **callback 强制拦截** |
| state key | `todos[:branch]` | `tasks[:branch]` | `goal[:branch]` |
| 典型用途 | 步骤可视、短清单 | 长看板、依赖编排 | 整件事是否算做完 |

> Todo / Task 管「步骤分解」,Goal 管「整体完成契约」。可组合使用,但避免让模型同时混用过多规划工具。

### GoalOptions 构造参数

通过 `setup_goal(agent, GoalOptions(...))` 配置:

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `state_key_prefix` | `str` | `"goal"` | state key 前缀;勿使用 `temp:` |
| `inject_guidance` | `bool` | `True` | 是否在 `before_model` 向 system instruction 注入 `DEFAULT_GUIDANCE` |
| `guidance` | `str` | `DEFAULT_GUIDANCE` | 注入的长文案(含串行调用 goal 工具等约定) |
| `max_retries` | `int` | `3` | 同 invocation 内拦截过早 final 的预算;耗尽后 fail-open |
| `nudge_template` | `str` | `DEFAULT_NUDGE` | 拦截后以 user-role 追加的提醒模板(支持 `{attempt}` / `{max_retries}` / `{objective}`) |
| `on_retry` | `Callable[[RetryEvent], None]` | `None` | 每次拦截或预算耗尽时的可观测回调 |

仅挂载模型工具、不要 enforcement 时,可直接 `tools=[GoalToolSet()]`,但不具备「未完成不许收尾」能力。

### 三个工具的 LLM 参数概要

**`create_goal`**

| 参数 | 必填 | 说明 |
|------|------|------|
| `objective` | 是 | 完成标准——「done」具体指什么 |

成功返回 `{message, goal}`;若已有 `active` 目标返回 `{error: "INVALID_STATE: ..."}`。

**`get_goal`**

无参数。有目标时返回 `{message, goal}`;无目标时返回 `{message: "No session goal is set."}`。

**`update_goal`**

| 参数 | 必填 | 说明 |
|------|------|------|
| `status` | 是 | `complete`(目标已达成)或 `blocked`(同一阻塞条件反复出现、无用户输入无法继续) |

成功返回 `{message, goal}`;无 active 目标或已是终态时返回 `{error: "INVALID_STATE: ..."}`。

**`GoalRecord` 字段**(JSON 使用 camelCase 别名持久化):

| 字段 | 说明 |
|------|------|
| `id` | 服务端分配的 uuid |
| `objective` | 完成标准文本 |
| `status` | `active` / `complete` / `blocked` |
| `createdAtUnix` / `updatedAtUnix` | 创建 / 最后更新时间(unix 秒) |
| `terminalAtUnix` | 进入终态的时间(可选) |

### enforcement 工作流程

```text
模型输出 final 文本(无 tool call,goal 仍 active)
after_model 判定为 premature final
抑制该 final(不提交为答案),retry_count += 1
before_model 注入 nudge,同 invocation 继续 agent loop
retry_count >= max_retries → fail-open,on_retry(reason="exhausted")
```

拦截条件(`_is_premature_final`):非 partial、无 error、content 含可见文本,且**不含** `function_call` / `function_response`。

### 使用方式

推荐一行挂载工具 + callbacks:

```python
from trpc_agent_sdk.agents import LlmAgent
from trpc_agent_sdk.models import OpenAIModel
from trpc_agent_sdk.tools.goal_tools import GoalOptions, RetryEvent, setup_goal

def on_retry(event: RetryEvent) -> None:
if event.reason == "blocked":
print(f"拦截过早收尾 (attempt {event.attempt_number}/{event.max_retries})")

agent = LlmAgent(
name="goal_agent",
model=OpenAIModel(model_name="...", api_key="...", base_url="..."),
instruction="多步工程任务请用 goal 工具跟踪完成状态。",
tools=[...], # 业务工具,如 BashTool / WriteTool
)
setup_goal(agent, GoalOptions(max_retries=3, on_retry=on_retry))
```

宿主在首轮前预注入目标(模型不调用 `create_goal`):

```python
from trpc_agent_sdk.tools.goal_tools import start_goal

goal = await start_goal(
session_service,
app_name="my_app",
user_id="user_1",
session_id=session_id,
objective="在当前目录创建 notes/ 并写入 summary.txt 与 example.py",
agent_name=agent.name, # 与 LlmAgent.name 一致,用于 branch 隔离
)
```

读回持久化目标(REST / 审计 / demo 收尾):

```python
from trpc_agent_sdk.tools.goal_tools import get_goal_record, render_goal

goal = get_goal_record(session, branch=agent.name)
print(render_goal(goal))
# ✅ Goal [complete]
# objective: ...
# created: 1782893110
# terminal: 1782893116
```

### Goal 工具族最佳实践

- **用 `setup_goal` 而非只挂 `GoalToolSet`**:只有 callbacks 才能实现「active 期间不许 final」
- **模型侧 vs 宿主侧**:Slash command、`/goal`、配置驱动任务用 `start_goal()`;让模型自主判断多步任务时用 `create_goal`
- **一次响应只调一个 goal 工具**:`DEFAULT_GUIDANCE` 要求串行语义;不要同轮 `create_goal` + `update_goal`
- **`blocked` 慎用**:仅当同一阻塞条件跨多次尝试仍无法推进、且需要用户输入或外部状态变化时使用;不要因为任务难、慢或不完整就标记 blocked
- **可观测性**:通过 `on_retry` 记录 `⚡ Premature final intercepted` 与预算耗尽,便于调优 prompt 或 `max_retries`
- **与 Todo / Task 分工**:Todo / Task 展示步骤与依赖;Goal 约束「整件事是否已真正完成」

### Goal 工具族完整示例

| 示例 | 说明 |
| --- | --- |
| [examples/goal_tools](../../../examples/goal_tools/) | Case 1:模型 `create_goal`;Case 2:宿主 `start_goal` 预注入;演示 enforcement 拦截与 `update_goal(complete)` |
4 changes: 4 additions & 0 deletions examples/goal_tools/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Set TRPC_AGENT_API_KEY、TRPC_AGENT_BASE_URL、TRPC_AGENT_MODEL_NAME
TRPC_AGENT_API_KEY=your-api-key
TRPC_AGENT_BASE_URL=your-base-url
TRPC_AGENT_MODEL_NAME=your-model-name
100 changes: 100 additions & 0 deletions examples/goal_tools/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Goal 工具示例

演示 **Goal 工具族**(`create_goal` / `get_goal` / `update_goal`):为会话设置一个持久目标,目标未完成前 Agent 应继续执行,而不是过早给出最终回复。示例同时挂载 `Bash` / `Write` / `Read` 完成真实的多步文件任务。

## 快速开始

```bash
# 在项目根目录安装
cd trpc-agent-python
python3 -m venv .venv && source .venv/bin/activate
pip3 install -e .

# 配置模型(examples/goal_tools/.env)
TRPC_AGENT_API_KEY=your-api-key
TRPC_AGENT_BASE_URL=your-base-url
TRPC_AGENT_MODEL_NAME=your-model-name

# 运行
cd examples/goal_tools
python3 run_agent.py
```

## 两个演示场景

脚本会**依次**跑两个独立 session:

| | Case 1 | Case 2 |
| --- | --- | --- |
| 谁设目标 | 模型调用 `create_goal` | 宿主调用 `start_goal()` 预注入 |
| 用户消息 | 普通多步任务(可提及 goal 能力) | 只描述要做的事,不提 goal 工具 |
| 典型流程 | `create_goal` → 写文件 → 验证 → `update_goal(complete)` | 写文件 → (可能被拦截)→ 补全 → `update_goal(complete)` |

只跑其中一个时,在 `run_agent.py` 的 `main()` 里注释掉对应 case。

## 日志说明

| 输出 | 含义 |
| --- | --- |
| `🔧 [Tool call]` | 模型发起的工具调用 |
| `📊 [Tool result]` | 工具返回摘要 |
| `💬 ...` | 工具返回里的 `message`(给模型看的提示) |
| `⚡ [Goal retry]` | 目标仍 active 时模型想提前收尾,被拦截并继续执行 |
| `🤖 Assistant:` | 本轮最终回复(正常应在 `update_goal(complete)` 之后) |
| `🎯 Persisted goal` | 从 session 读回的目标状态 |

终端较窄时,长参数的工具调用行可能折行,看起来像重复打印,以 `📊` 条数为准。

## 运行结果(实测摘录)

### Case 1:模型自己设 Goal

```text
🔧 [Tool call] create_goal({...})
📊 [Tool result] created id=... status=active objective='...'
💬 Goal created and is now active. Keep working until it is genuinely met, then call update_goal('complete').

🔧 [Tool call] Write({'path': 'mypkg/__init__.py', ...})
🔧 [Tool call] Write({'path': 'mypkg/utils.py', ...})
🔧 [Tool call] Write({'path': 'mypkg/README.md', ...})
...
🔧 [Tool call] Bash({'command': 'ls -la mypkg/ && ... python -c "import mypkg; ..."'})
🔧 [Tool call] update_goal({'status': 'complete'})

🤖 Assistant: 工具包 `mypkg/` 已搭建完成并验证通过 ✅

🎯 Persisted goal (read from session):
✅ Goal [complete]
```

本例未触发 `⚡ [Goal retry]`,说明模型在标记完成前没有过早收尾。

### Case 2:宿主预注入 Goal

```text
🎯 Goal pre-injected by host:
objective: '在当前目录创建 notes/ 目录,其中包含两个文件:...'
status: active

🔧 [Tool call] Write({'path': 'notes/summary.txt', ...})
⚡ [Goal retry] Premature final intercepted (attempt 1/3). Objective: '...'

🔧 [Tool call] Write({'path': 'notes/example.py', ...})
⚡ [Goal retry] Premature final intercepted (attempt 2/3). Objective: '...'

🔧 [Tool call] get_goal({})
🔧 [Tool call] Bash({'command': 'cd notes && python example.py', ...})
🔧 [Tool call] update_goal({'status': 'complete'})

🤖 Assistant: 已完成。创建了 `notes/` 目录,其中包含两个文件:...

🎯 Persisted goal (read from session):
✅ Goal [complete]
```

Case 2 里写完第一个文件后模型就想总结,**Goal enforcement 拦截了 2 次**,随后补写 `example.py`、运行验证,再 `update_goal(complete)`——这正是 Goal 能力的预期表现。

## 关键文件

- [`run_agent.py`](./run_agent.py) — 入口,驱动两个 case 并打印事件
- [`agent/agent.py`](./agent/agent.py) — 组装 Agent,调用 `setup_goal()` 挂载 Goal 能力
5 changes: 5 additions & 0 deletions examples/goal_tools/agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
76 changes: 76 additions & 0 deletions examples/goal_tools/agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Agent module for the Goal tools example."""

import os

from trpc_agent_sdk.agents import LlmAgent
from trpc_agent_sdk.models import LLMModel
from trpc_agent_sdk.models import OpenAIModel
from trpc_agent_sdk.tools.file_tools import BashTool
from trpc_agent_sdk.tools.file_tools import ReadTool
from trpc_agent_sdk.tools.file_tools import WriteTool
from trpc_agent_sdk.tools.goal_tools import GoalOptions
from trpc_agent_sdk.tools.goal_tools import RetryEvent
from trpc_agent_sdk.tools.goal_tools import setup_goal

from .config import get_model_config
from .prompts import INSTRUCTION


def _create_model() -> LLMModel:
api_key, url, model_name = get_model_config()
return OpenAIModel(model_name=model_name, api_key=api_key, base_url=url)


def on_retry(event: RetryEvent) -> None:
"""Observability callback: called every time the retry intercepts a premature final."""
if event.reason == "blocked":
print(
f" ⚡ [Goal retry] Premature final intercepted "
f"(attempt {event.attempt_number}/{event.max_retries}). "
f"Objective: {event.goal.objective!r}"
)
else:
print(
f" ⚠️ [Goal retry] Budget exhausted ({event.max_retries} retries). "
f"Letting final response through."
)


def create_goal_agent(work_dir: str | None = None) -> LlmAgent:
"""Build an agent with the Goal capability mounted via ``setup_goal``.

The agent exposes ``create_goal`` / ``get_goal`` / ``update_goal`` tools
plus file-system tools for actually executing multi-step work. The
retry callbacks (guidance injection + premature-final interception)
are installed automatically by :func:`setup_goal`.

Args:
work_dir: Working directory for Bash / Write / Read tools.
"""
cwd = work_dir or os.getcwd()
agent = LlmAgent(
name="goal_agent",
description="Engineering assistant that pursues a persistent session goal step by step.",
model=_create_model(),
instruction=INSTRUCTION,
tools=[
BashTool(cwd=cwd),
WriteTool(cwd=cwd),
ReadTool(cwd=cwd),
],
)
opts = GoalOptions(
max_retries=3,
on_retry=on_retry,
)
setup_goal(agent, opts)
return agent


goal_agent = create_goal_agent()
root_agent = goal_agent
21 changes: 21 additions & 0 deletions examples/goal_tools/agent/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Agent config module."""

import os


def get_model_config() -> tuple[str, str, str]:
"""Get LLM model config from environment variables."""
api_key = os.getenv("TRPC_AGENT_API_KEY", "")
url = os.getenv("TRPC_AGENT_BASE_URL", "")
model_name = os.getenv("TRPC_AGENT_MODEL_NAME", "")
if not api_key or not url or not model_name:
raise ValueError(
"TRPC_AGENT_API_KEY, TRPC_AGENT_BASE_URL, and TRPC_AGENT_MODEL_NAME "
"must be set in environment variables"
)
return api_key, url, model_name
9 changes: 9 additions & 0 deletions examples/goal_tools/agent/prompts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Tencent is pleased to support the open source community by making tRPC-Agent-Python available.
#
# Copyright (C) 2026 Tencent. All rights reserved.
#
# tRPC-Agent-Python is licensed under Apache-2.0.
"""Prompts for the Goal tools demo agent."""

INSTRUCTION = """You are a rigorous engineering assistant that can work toward session goals. """

Loading
Loading