|
|
@@ -25,7 +25,7 @@
|
|
|
from reson_agent import tool, ToolResult, ToolContext
|
|
|
|
|
|
@tool()
|
|
|
-async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
|
|
|
+async def my_tool(arg: str, context: Optional[ToolContext] = None) -> ToolResult:
|
|
|
return ToolResult(
|
|
|
title="Success",
|
|
|
output="Result content"
|
|
|
@@ -44,16 +44,53 @@ async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
|
|
|
1. 定义工具
|
|
|
↓ @tool() 装饰器
|
|
|
2. 自动注册到 ToolRegistry
|
|
|
- ↓ 生成 OpenAI Tool Schema
|
|
|
+ ↓ 生成 OpenAI Tool Schema(跳过 hidden_params)
|
|
|
3. LLM 选择工具并生成参数
|
|
|
↓ registry.execute(name, args)
|
|
|
-4. 注入 uid 和 context
|
|
|
+4. 注入框架参数(hidden_params + inject_params)
|
|
|
↓ 调用工具函数
|
|
|
5. 返回 ToolResult
|
|
|
↓ 转换为 LLM 消息
|
|
|
6. 添加到对话历史
|
|
|
```
|
|
|
|
|
|
+### 参数注入机制
|
|
|
+
|
|
|
+工具参数分为三类:
|
|
|
+
|
|
|
+1. **业务参数**:LLM 可见,由 LLM 填写(如 `query`, `limit`)
|
|
|
+2. **隐藏参数**:LLM 不可见,框架自动注入(如 `context`, `uid`)
|
|
|
+3. **注入参数**:LLM 可见但有默认值,框架自动注入默认值(如 `owner`, `tags`)
|
|
|
+
|
|
|
+```python
|
|
|
+@tool(
|
|
|
+ hidden_params=["context", "uid"], # 不生成 schema,LLM 看不到
|
|
|
+ inject_params={ # 自动注入默认值
|
|
|
+ "owner": lambda ctx: ctx.config.knowledge.get_owner(),
|
|
|
+ "tags": lambda ctx, args: {**ctx.config.default_tags, **args.get("tags", {})},
|
|
|
+ }
|
|
|
+)
|
|
|
+async def knowledge_save(
|
|
|
+ task: str, # 业务参数:LLM 填写
|
|
|
+ content: str, # 业务参数:LLM 填写
|
|
|
+ types: List[str], # 业务参数:LLM 填写
|
|
|
+ tags: Optional[Dict] = None, # 注入参数:LLM 可填,框架提供默认值
|
|
|
+ owner: Optional[str] = None, # 注入参数:LLM 可填,框架提供默认值
|
|
|
+ context: Optional[ToolContext] = None, # 隐藏参数:LLM 看不到
|
|
|
+ uid: str = "", # 隐藏参数:LLM 看不到
|
|
|
+) -> ToolResult:
|
|
|
+ """保存知识到知识库"""
|
|
|
+ ...
|
|
|
+```
|
|
|
+
|
|
|
+**注入时机**:
|
|
|
+- Schema 生成时:跳过 `hidden_params`,不暴露给 LLM
|
|
|
+- 工具执行前:注入 `hidden_params` 和 `inject_params` 的默认值
|
|
|
+
|
|
|
+**实现位置**:
|
|
|
+- Schema 生成:`agent/tools/schema.py:SchemaGenerator.generate()`
|
|
|
+- 参数注入:`agent/tools/registry.py:ToolRegistry.execute()`
|
|
|
+
|
|
|
---
|
|
|
|
|
|
## 定义工具
|
|
|
@@ -64,23 +101,24 @@ async def my_tool(arg: str, ctx: ToolContext) -> ToolResult:
|
|
|
from reson_agent import tool
|
|
|
|
|
|
@tool()
|
|
|
-async def hello(name: str, uid: str = "") -> str:
|
|
|
+async def hello(name: str) -> str:
|
|
|
"""向用户问好"""
|
|
|
return f"Hello, {name}!"
|
|
|
```
|
|
|
|
|
|
**要点**:
|
|
|
-- `uid` 参数由框架自动注入(用户不传递)
|
|
|
- 可以是同步或异步函数
|
|
|
- 返回值自动序列化为 JSON
|
|
|
+- 所有参数默认对 LLM 可见
|
|
|
|
|
|
-### 带完整注释
|
|
|
+### 带框架参数
|
|
|
|
|
|
```python
|
|
|
-@tool()
|
|
|
+@tool(hidden_params=["context", "uid"])
|
|
|
async def search_notes(
|
|
|
query: str,
|
|
|
limit: int = 10,
|
|
|
+ context: Optional[ToolContext] = None,
|
|
|
uid: str = ""
|
|
|
) -> str:
|
|
|
"""
|
|
|
@@ -89,14 +127,50 @@ async def search_notes(
|
|
|
Args:
|
|
|
query: 搜索关键词
|
|
|
limit: 返回结果数量
|
|
|
+ """
|
|
|
+ # context 和 uid 由框架注入,LLM 看不到这两个参数
|
|
|
+ ...
|
|
|
+```
|
|
|
|
|
|
- Returns:
|
|
|
- JSON 格式的搜索结果
|
|
|
+### 带参数注入
|
|
|
+
|
|
|
+```python
|
|
|
+@tool(
|
|
|
+ hidden_params=["context"],
|
|
|
+ inject_params={
|
|
|
+ "owner": lambda ctx: ctx.config.knowledge.get_owner(),
|
|
|
+ "tags": lambda ctx, args: {**ctx.config.default_tags, **args.get("tags", {})},
|
|
|
+ }
|
|
|
+)
|
|
|
+async def knowledge_save(
|
|
|
+ task: str,
|
|
|
+ content: str,
|
|
|
+ types: List[str],
|
|
|
+ tags: Optional[Dict] = None, # LLM 可填,框架提供默认值
|
|
|
+ owner: Optional[str] = None, # LLM 可填,框架提供默认值
|
|
|
+ context: Optional[ToolContext] = None
|
|
|
+) -> ToolResult:
|
|
|
"""
|
|
|
- # 支持自动从 docstring 提取 function description 和 parameter descriptions
|
|
|
+ 保存知识
|
|
|
+
|
|
|
+ Args:
|
|
|
+ task: 任务描述
|
|
|
+ content: 知识内容
|
|
|
+ types: 知识类型
|
|
|
+ tags: 业务标签(可选,有默认值)
|
|
|
+ owner: 所有者(可选,有默认值)
|
|
|
+ """
|
|
|
+ # owner 和 tags 如果 LLM 未提供,框架会注入默认值
|
|
|
...
|
|
|
```
|
|
|
|
|
|
+**注入规则**:
|
|
|
+- `inject_params` 的 value 可以是:
|
|
|
+ - `lambda ctx: ...` - 从 context 计算
|
|
|
+ - `lambda ctx, args: ...` - 从 context 和已有参数计算
|
|
|
+ - 字符串 - 直接使用该值
|
|
|
+- 注入时机:工具执行前,使用 `setdefault` 注入(不覆盖 LLM 提供的值)
|
|
|
+
|
|
|
### 带 UI 元数据
|
|
|
|
|
|
```python
|
|
|
@@ -228,16 +302,16 @@ async def generate_report() -> ToolResult:
|
|
|
|
|
|
### 基本概念
|
|
|
|
|
|
-工具函数可以声明需要 `ToolContext` 参数,框架自动注入。
|
|
|
+工具函数可以声明需要 `ToolContext` 参数,框架自动注入。需要在 `@tool()` 装饰器中声明 `hidden_params=["context"]`,使其对 LLM 不可见。
|
|
|
|
|
|
```python
|
|
|
from reson_agent import ToolContext
|
|
|
|
|
|
-@tool()
|
|
|
-async def get_current_state(ctx: ToolContext) -> ToolResult:
|
|
|
+@tool(hidden_params=["context"])
|
|
|
+async def get_current_state(context: Optional[ToolContext] = None) -> ToolResult:
|
|
|
return ToolResult(
|
|
|
title="Current state",
|
|
|
- output=f"Trace ID: {ctx.trace_id}\nStep ID: {ctx.step_id}"
|
|
|
+ output=f"Trace ID: {context.trace_id}\nStep ID: {context.step_id}"
|
|
|
)
|
|
|
```
|
|
|
|
|
|
@@ -250,56 +324,66 @@ class ToolContext(Protocol):
|
|
|
step_id: str # 当前 Step ID
|
|
|
uid: Optional[str] # 用户 ID
|
|
|
|
|
|
+ # 扩展字段(由 runner 注入)
|
|
|
+ store: Optional[TraceStore] # Trace 存储
|
|
|
+ runner: Optional[AgentRunner] # Runner 实例
|
|
|
+ goal_tree: Optional[GoalTree] # 目标树
|
|
|
+ goal_id: Optional[str] # 当前 Goal ID
|
|
|
+ config: Optional[RunConfig] # 运行配置
|
|
|
+
|
|
|
# 浏览器相关(Browser-Use 集成)
|
|
|
browser_session: Optional[Any] # 浏览器会话
|
|
|
page_url: Optional[str] # 当前页面 URL
|
|
|
file_system: Optional[Any] # 文件系统访问
|
|
|
sensitive_data: Optional[Dict] # 敏感数据
|
|
|
|
|
|
- # 扩展字段
|
|
|
- context: Optional[Dict[str, Any]] # 额外上下文
|
|
|
+ # 额外上下文
|
|
|
+ context: Optional[Dict[str, Any]] # 额外上下文数据
|
|
|
```
|
|
|
|
|
|
### 使用示例
|
|
|
|
|
|
```python
|
|
|
-@tool()
|
|
|
-async def analyze_current_page(ctx: ToolContext) -> ToolResult:
|
|
|
+@tool(hidden_params=["context"])
|
|
|
+async def analyze_current_page(context: Optional[ToolContext] = None) -> ToolResult:
|
|
|
"""分析当前浏览器页面"""
|
|
|
|
|
|
- if not ctx.browser_session:
|
|
|
+ if not context or not context.browser_session:
|
|
|
return ToolResult(
|
|
|
title="Error",
|
|
|
error="Browser session not available"
|
|
|
)
|
|
|
|
|
|
# 使用浏览器会话
|
|
|
- page_content = await ctx.browser_session.get_content()
|
|
|
+ page_content = await context.browser_session.get_content()
|
|
|
|
|
|
return ToolResult(
|
|
|
- title=f"Analyzed {ctx.page_url}",
|
|
|
+ title=f"Analyzed {context.page_url}",
|
|
|
output=page_content,
|
|
|
- long_term_memory=f"Analyzed page at {ctx.page_url}"
|
|
|
+ long_term_memory=f"Analyzed page at {context.page_url}"
|
|
|
)
|
|
|
```
|
|
|
|
|
|
### 创建 ToolContext
|
|
|
|
|
|
-```python
|
|
|
-from reson_agent import ToolContextImpl
|
|
|
+Runner 在执行工具时自动创建并注入 context:
|
|
|
|
|
|
-ctx = ToolContextImpl(
|
|
|
- trace_id="trace_123",
|
|
|
- step_id="step_456",
|
|
|
- uid="user_789",
|
|
|
- page_url="https://example.com"
|
|
|
-)
|
|
|
+```python
|
|
|
+# 在 AgentRunner._agent_loop 中
|
|
|
+context = {
|
|
|
+ "store": self.trace_store,
|
|
|
+ "trace_id": trace_id,
|
|
|
+ "goal_id": current_goal_id,
|
|
|
+ "runner": self,
|
|
|
+ "goal_tree": goal_tree,
|
|
|
+ "config": config,
|
|
|
+}
|
|
|
|
|
|
-# 执行工具
|
|
|
-result = await registry.execute(
|
|
|
- "analyze_current_page",
|
|
|
- arguments={},
|
|
|
- context=ctx
|
|
|
+result = await self.tools.execute(
|
|
|
+ tool_name,
|
|
|
+ tool_args,
|
|
|
+ uid=config.uid or "",
|
|
|
+ context=context
|
|
|
)
|
|
|
```
|
|
|
|