""" 工具系统完整示例 本文件展示 @tool 装饰器的所有用法,包括: ## 基础功能 1. 最简形式 2. 带 i18n 展示信息 3. 带可编辑参数 4. 需要用户确认 5. 带 context 参数 6. 同步工具 7. 复杂返回类型 ## 高级功能 8. 域名过滤(URL Patterns) 9. 敏感数据处理( 占位符 + TOTP) 10. 工具使用统计 11. 组合所有功能 注意: - uid 参数会由框架自动注入,不需要用户传递 - context 参数用于传递额外上下文(如浏览器会话、当前 URL 等) - 返回值可以是字符串、字典或 ToolResult """ import asyncio import json from typing import List, Dict, Any, Optional from agent import tool, ToolResult, ToolContext, get_tool_registry # ============================================================ # 基础功能示例 # ============================================================ # 1. 最简形式 @tool() async def hello_world(name: str, uid: str = "") -> Dict[str, str]: """ 最简单的工具示例 Args: name: 要问候的名字 uid: 用户ID(自动注入) Returns: 包含问候语的字典 """ return {"greeting": f"Hello, {name}!"} # 2. 带 i18n 展示信息的工具 @tool( display={ "zh": { "name": "搜索内容", "params": { "query": "搜索关键词", "limit": "返回数量" } }, "en": { "name": "Search Content", "params": { "query": "Search query", "limit": "Number of results" } } } ) async def search_content( query: str, limit: int = 10, uid: str = "" ) -> List[Dict[str, Any]]: """ 搜索用户的内容 使用语义搜索查找相关内容。display 参数用于前端展示: - 工具名称会根据用户语言显示为"搜索内容"或"Search Content" - 参数名称也会相应翻译 Args: query: 搜索查询文本 limit: 返回结果数量(默认10) uid: 用户ID(自动注入) Returns: 搜索结果列表,每个包含 id, title, content, score """ # 实际实现中会调用向量搜索 return [ { "id": "doc_001", "title": f"关于 {query} 的文档", "content": f"这是与 {query} 相关的内容...", "score": 0.95 } ] # 3. 带可编辑参数的工具 @tool( editable_params=["query", "filters"], display={ "zh": { "name": "高级搜索", "params": { "query": "搜索关键词", "filters": "过滤条件", "sort_by": "排序方式" } } } ) async def advanced_search( query: str, filters: Optional[Dict[str, Any]] = None, sort_by: str = "relevance", limit: int = 20, uid: str = "" ) -> Dict[str, Any]: """ 高级搜索工具(允许用户编辑参数) editable_params 指定哪些参数允许用户在 LLM 生成后编辑: - LLM 会先生成 query 和 filters - 用户可以在确认前修改这些参数 - 适用于搜索、创建等需要用户微调的场景 Args: query: 搜索查询 filters: 过滤条件(如 {"type": "note", "date_range": "7d"}) sort_by: 排序方式(relevance/date/title) limit: 返回数量 uid: 用户ID(自动注入) Returns: 搜索结果和元数据 """ return { "results": [ {"id": "1", "title": "Result 1", "score": 0.9}, {"id": "2", "title": "Result 2", "score": 0.8}, ], "total": 42, "query": query, "filters_applied": filters or {}, "sort_by": sort_by } # 4. 需要用户确认的危险操作 @tool( requires_confirmation=True, display={ "zh": { "name": "删除内容", "params": { "content_id": "内容ID", "permanent": "永久删除" } } } ) async def delete_content( content_id: str, permanent: bool = False, uid: str = "" ) -> Dict[str, Any]: """ 删除内容(需要用户确认) requires_confirmation=True 表示这是一个危险操作: - LLM 调用此工具时,不会立即执行 - 会先向用户展示操作详情,等待确认 - 用户确认后才会真正执行 适用场景:删除操作、发送消息、修改重要设置、任何不可逆操作 Args: content_id: 要删除的内容ID permanent: 是否永久删除(False=移到回收站) uid: 用户ID(自动注入) Returns: 删除结果 """ return { "success": True, "content_id": content_id, "permanent": permanent, "message": f"内容 {content_id} 已{'永久删除' if permanent else '移到回收站'}" } # 5. 带 context 参数的工具 @tool( display={ "zh": {"name": "获取相关推荐", "params": {"top_k": "推荐数量"}} } ) async def get_recommendations( top_k: int = 5, uid: str = "", context: Optional[Dict[str, Any]] = None ) -> List[Dict[str, Any]]: """ 获取相关推荐(使用 context 获取额外信息) context 参数用于传递执行上下文,由框架自动注入: - 当前阅读位置 (current_location) - 当前会话 ID (session_id) - 排除的内容 ID (exclude_ids) Args: top_k: 返回推荐数量 uid: 用户ID(自动注入) context: 执行上下文(自动注入) Returns: 推荐列表 """ current_location = None if context: current_location = context.get("current_location") return [ { "id": "rec_001", "title": "推荐内容 1", "reason": f"基于当前位置 {current_location}" if current_location else "基于您的兴趣" } ] # 6. 同步工具(非 async) @tool() def format_text( text: str, format_type: str = "markdown", uid: str = "" ) -> str: """ 格式化文本(同步工具) 不需要 async 的工具可以定义为普通函数。 框架会自动检测并正确调用。 适用于:纯计算操作、文本处理、不需要 I/O 的操作 Args: text: 要格式化的文本 format_type: 格式类型(markdown/plain/html) uid: 用户ID(自动注入) Returns: 格式化后的文本 """ if format_type == "markdown": return f"**{text}**" elif format_type == "html": return f"

{text}

" else: return text # 7. 使用 ToolResult 的工具 @tool() async def analyze_content( content_id: str, analysis_types: Optional[List[str]] = None, uid: str = "" ) -> ToolResult: """ 分析内容(使用 ToolResult) ToolResult 支持双层记忆管理: - output: 完整结果(可能很长) - long_term_memory: 简短摘要(永久保存) Args: content_id: 要分析的内容ID analysis_types: 分析类型列表(sentiment/keywords/summary) uid: 用户ID(自动注入) Returns: ToolResult 包含分析结果 """ types = analysis_types or ["sentiment", "keywords"] result = { "content_id": content_id, "analyses": {} } if "sentiment" in types: result["analyses"]["sentiment"] = { "score": 0.8, "label": "positive", "confidence": 0.92 } if "keywords" in types: result["analyses"]["keywords"] = [ {"word": "AI", "weight": 0.9}, {"word": "学习", "weight": 0.7} ] return ToolResult( title=f"Analysis of {content_id}", output=json.dumps(result, indent=2, ensure_ascii=False), long_term_memory=f"Analyzed {content_id}: {', '.join(types)}", metadata={"types": types} ) # ============================================================ # 高级功能示例 # ============================================================ # 8. 域名过滤示例 @tool(url_patterns=["*.google.com", "www.google.*"]) async def google_search(query: str, uid: str = "") -> ToolResult: """ Google 搜索(仅在 Google 页面可用) 使用 url_patterns 限制工具只在特定域名显示。 在 Google 页面时,此工具会出现在可用工具列表中。 在其他页面时,此工具会被过滤掉。 Args: query: 搜索查询 uid: 用户ID(自动注入) Returns: 搜索结果 """ return ToolResult( title="Google Search", output=f"Searching Google for: {query}", long_term_memory=f"Searched Google for '{query}'" ) @tool(url_patterns=["*.github.com"]) async def create_github_issue( title: str, body: str, uid: str = "" ) -> ToolResult: """ 创建 GitHub Issue(仅在 GitHub 页面可用) Args: title: Issue 标题 body: Issue 内容 uid: 用户ID(自动注入) Returns: 创建结果 """ return ToolResult( title="Issue Created", output=f"Created issue: {title}", long_term_memory=f"Created GitHub issue: {title}" ) @tool() # 无 url_patterns,所有页面都可用 async def take_screenshot(uid: str = "") -> ToolResult: """截图(所有页面都可用)""" return ToolResult( title="Screenshot", output="Screenshot taken", attachments=["screenshot_001.png"] ) # 9. 敏感数据处理示例 @tool(url_patterns=["*.github.com"]) async def github_login( username: str, password: str, totp_code: str, uid: str = "" ) -> ToolResult: """ GitHub 登录(支持敏感数据占位符) LLM 会输出类似: { "username": "user@example.com", "password": "github_password", "totp_code": "github_2fa_bu_2fa_code" } 执行时会自动替换为实际值。 Args: username: 用户名 password: 密码(可以是占位符) totp_code: TOTP 验证码(可以是占位符,自动生成) uid: 用户ID(自动注入) Returns: 登录结果 """ # 注意:password 和 totp_code 在到达这里时已经被替换 return ToolResult( title="Login Successful", output=f"Logged in as {username}", long_term_memory=f"Logged in to GitHub as {username}" ) # 10. 组合所有功能 @tool( url_patterns=["*.example.com"], requires_confirmation=True, editable_params=["message"], display={ "zh": { "name": "发送认证消息", "params": { "recipient": "接收者", "message": "消息内容", "api_key": "API密钥" } } } ) async def send_authenticated_message( recipient: str, message: str, api_key: str, ctx: ToolContext, uid: str = "" ) -> ToolResult: """ 发送消息(组合多个功能) 展示所有高级功能: - 仅在 example.com 可用(域名过滤) - 需要用户确认(危险操作) - 消息可编辑(用户微调) - API key 使用敏感数据占位符 - 使用 ToolContext 获取上下文 Args: recipient: 接收者 message: 消息内容 api_key: API密钥(可以是占位符) ctx: 工具上下文 uid: 用户ID(自动注入) Returns: 发送结果 """ # api_key 会从 api_key 替换为实际值 # ctx 包含 page_url, browser_session 等信息 return ToolResult( title="Message Sent", output=f"Sent to {recipient}: {message}", long_term_memory=f"Sent message to {recipient} on {ctx.page_url}", metadata={"recipient": recipient} ) # ============================================================ # 使用示例 # ============================================================ async def main(): registry = get_tool_registry() print("=" * 60) print("工具系统完整示例") print("=" * 60) # ============================================================ # 示例 1:基础工具调用 # ============================================================ print("\n1. 基础工具调用") print("-" * 60) result = await registry.execute("hello_world", {"name": "Alice"}) print(f"hello_world: {result}") result = await registry.execute("search_content", {"query": "Python", "limit": 5}) print(f"search_content: {result}") # ============================================================ # 示例 2:域名过滤 # ============================================================ print("\n\n2. 域名过滤示例") print("-" * 60) # 在 Google 页面 google_url = "https://www.google.com/search?q=test" google_tools = registry.get_tool_names(google_url) print(f"在 {google_url} 可用的工具:") print(f" 包含 google_search: {'google_search' in google_tools}") # 在 GitHub 页面 github_url = "https://github.com/user/repo" github_tools = registry.get_tool_names(github_url) print(f"\n在 {github_url} 可用的工具:") print(f" 包含 create_github_issue: {'create_github_issue' in github_tools}") print(f" 包含 google_search: {'google_search' in github_tools}") # ============================================================ # 示例 3:敏感数据处理 # ============================================================ print("\n\n3. 敏感数据处理示例") print("-" * 60) # 配置敏感数据 sensitive_data = { "*.github.com": { "github_password": "my_secret_password", "github_2fa_bu_2fa_code": "JBSWY3DPEHPK3PXP" # TOTP secret } } # 模拟 LLM 输出(包含占位符) llm_output_args = { "username": "user@example.com", "password": "github_password", "totp_code": "github_2fa_bu_2fa_code" } print("LLM 输出的参数(包含占位符):") print(f" {llm_output_args}") # 执行工具(自动替换敏感数据) result = await registry.execute( "github_login", llm_output_args, context={"page_url": "https://github.com/login"}, sensitive_data=sensitive_data ) print(f"\n执行结果(密码已替换):") print(f" {result}") # ============================================================ # 示例 4:工具统计 # ============================================================ print("\n\n4. 工具统计示例") print("-" * 60) # 模拟多次调用 for i in range(5): await registry.execute("google_search", {"query": f"test {i}"}) await registry.execute("take_screenshot", {}) await registry.execute("take_screenshot", {}) # 查看统计 stats = registry.get_stats() print("工具使用统计:") for tool_name, tool_stats in stats.items(): if tool_stats["call_count"] > 0: print(f"\n {tool_name}:") print(f" 调用次数: {tool_stats['call_count']}") print(f" 成功率: {tool_stats['success_rate']:.1%}") print(f" 平均执行时间: {tool_stats['average_duration']:.3f}s") # 获取 Top 工具 print("\n\nTop 3 最常用工具:") top_tools = registry.get_top_tools(limit=3, by="call_count") for i, tool_name in enumerate(top_tools, 1): tool_stats = stats[tool_name] print(f" {i}. {tool_name} ({tool_stats['call_count']} 次调用)") if __name__ == "__main__": asyncio.run(main())