tools_examples.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. """
  2. 工具系统完整示例
  3. 本文件展示 @tool 装饰器的所有用法,包括:
  4. ## 基础功能
  5. 1. 最简形式
  6. 2. 带 i18n 展示信息
  7. 3. 带可编辑参数
  8. 4. 需要用户确认
  9. 5. 带 context 参数
  10. 6. 同步工具
  11. 7. 复杂返回类型
  12. ## 高级功能
  13. 8. 域名过滤(URL Patterns)
  14. 9. 敏感数据处理(<secret> 占位符 + TOTP)
  15. 10. 工具使用统计
  16. 11. 组合所有功能
  17. 注意:
  18. - uid 参数会由框架自动注入,不需要用户传递
  19. - context 参数用于传递额外上下文(如浏览器会话、当前 URL 等)
  20. - 返回值可以是字符串、字典或 ToolResult
  21. """
  22. import asyncio
  23. import json
  24. from typing import List, Dict, Any, Optional
  25. from reson_agent import tool, ToolResult, ToolContext, get_tool_registry
  26. # ============================================================
  27. # 基础功能示例
  28. # ============================================================
  29. # 1. 最简形式
  30. @tool()
  31. async def hello_world(name: str, uid: str = "") -> Dict[str, str]:
  32. """
  33. 最简单的工具示例
  34. Args:
  35. name: 要问候的名字
  36. uid: 用户ID(自动注入)
  37. Returns:
  38. 包含问候语的字典
  39. """
  40. return {"greeting": f"Hello, {name}!"}
  41. # 2. 带 i18n 展示信息的工具
  42. @tool(
  43. display={
  44. "zh": {
  45. "name": "搜索内容",
  46. "params": {
  47. "query": "搜索关键词",
  48. "limit": "返回数量"
  49. }
  50. },
  51. "en": {
  52. "name": "Search Content",
  53. "params": {
  54. "query": "Search query",
  55. "limit": "Number of results"
  56. }
  57. }
  58. }
  59. )
  60. async def search_content(
  61. query: str,
  62. limit: int = 10,
  63. uid: str = ""
  64. ) -> List[Dict[str, Any]]:
  65. """
  66. 搜索用户的内容
  67. 使用语义搜索查找相关内容。display 参数用于前端展示:
  68. - 工具名称会根据用户语言显示为"搜索内容"或"Search Content"
  69. - 参数名称也会相应翻译
  70. Args:
  71. query: 搜索查询文本
  72. limit: 返回结果数量(默认10)
  73. uid: 用户ID(自动注入)
  74. Returns:
  75. 搜索结果列表,每个包含 id, title, content, score
  76. """
  77. # 实际实现中会调用向量搜索
  78. return [
  79. {
  80. "id": "doc_001",
  81. "title": f"关于 {query} 的文档",
  82. "content": f"这是与 {query} 相关的内容...",
  83. "score": 0.95
  84. }
  85. ]
  86. # 3. 带可编辑参数的工具
  87. @tool(
  88. editable_params=["query", "filters"],
  89. display={
  90. "zh": {
  91. "name": "高级搜索",
  92. "params": {
  93. "query": "搜索关键词",
  94. "filters": "过滤条件",
  95. "sort_by": "排序方式"
  96. }
  97. }
  98. }
  99. )
  100. async def advanced_search(
  101. query: str,
  102. filters: Optional[Dict[str, Any]] = None,
  103. sort_by: str = "relevance",
  104. limit: int = 20,
  105. uid: str = ""
  106. ) -> Dict[str, Any]:
  107. """
  108. 高级搜索工具(允许用户编辑参数)
  109. editable_params 指定哪些参数允许用户在 LLM 生成后编辑:
  110. - LLM 会先生成 query 和 filters
  111. - 用户可以在确认前修改这些参数
  112. - 适用于搜索、创建等需要用户微调的场景
  113. Args:
  114. query: 搜索查询
  115. filters: 过滤条件(如 {"type": "note", "date_range": "7d"})
  116. sort_by: 排序方式(relevance/date/title)
  117. limit: 返回数量
  118. uid: 用户ID(自动注入)
  119. Returns:
  120. 搜索结果和元数据
  121. """
  122. return {
  123. "results": [
  124. {"id": "1", "title": "Result 1", "score": 0.9},
  125. {"id": "2", "title": "Result 2", "score": 0.8},
  126. ],
  127. "total": 42,
  128. "query": query,
  129. "filters_applied": filters or {},
  130. "sort_by": sort_by
  131. }
  132. # 4. 需要用户确认的危险操作
  133. @tool(
  134. requires_confirmation=True,
  135. display={
  136. "zh": {
  137. "name": "删除内容",
  138. "params": {
  139. "content_id": "内容ID",
  140. "permanent": "永久删除"
  141. }
  142. }
  143. }
  144. )
  145. async def delete_content(
  146. content_id: str,
  147. permanent: bool = False,
  148. uid: str = ""
  149. ) -> Dict[str, Any]:
  150. """
  151. 删除内容(需要用户确认)
  152. requires_confirmation=True 表示这是一个危险操作:
  153. - LLM 调用此工具时,不会立即执行
  154. - 会先向用户展示操作详情,等待确认
  155. - 用户确认后才会真正执行
  156. 适用场景:删除操作、发送消息、修改重要设置、任何不可逆操作
  157. Args:
  158. content_id: 要删除的内容ID
  159. permanent: 是否永久删除(False=移到回收站)
  160. uid: 用户ID(自动注入)
  161. Returns:
  162. 删除结果
  163. """
  164. return {
  165. "success": True,
  166. "content_id": content_id,
  167. "permanent": permanent,
  168. "message": f"内容 {content_id} 已{'永久删除' if permanent else '移到回收站'}"
  169. }
  170. # 5. 带 context 参数的工具
  171. @tool(
  172. display={
  173. "zh": {"name": "获取相关推荐", "params": {"top_k": "推荐数量"}}
  174. }
  175. )
  176. async def get_recommendations(
  177. top_k: int = 5,
  178. uid: str = "",
  179. context: Optional[Dict[str, Any]] = None
  180. ) -> List[Dict[str, Any]]:
  181. """
  182. 获取相关推荐(使用 context 获取额外信息)
  183. context 参数用于传递执行上下文,由框架自动注入:
  184. - 当前阅读位置 (current_location)
  185. - 当前会话 ID (session_id)
  186. - 排除的内容 ID (exclude_ids)
  187. Args:
  188. top_k: 返回推荐数量
  189. uid: 用户ID(自动注入)
  190. context: 执行上下文(自动注入)
  191. Returns:
  192. 推荐列表
  193. """
  194. current_location = None
  195. if context:
  196. current_location = context.get("current_location")
  197. return [
  198. {
  199. "id": "rec_001",
  200. "title": "推荐内容 1",
  201. "reason": f"基于当前位置 {current_location}" if current_location else "基于您的兴趣"
  202. }
  203. ]
  204. # 6. 同步工具(非 async)
  205. @tool()
  206. def format_text(
  207. text: str,
  208. format_type: str = "markdown",
  209. uid: str = ""
  210. ) -> str:
  211. """
  212. 格式化文本(同步工具)
  213. 不需要 async 的工具可以定义为普通函数。
  214. 框架会自动检测并正确调用。
  215. 适用于:纯计算操作、文本处理、不需要 I/O 的操作
  216. Args:
  217. text: 要格式化的文本
  218. format_type: 格式类型(markdown/plain/html)
  219. uid: 用户ID(自动注入)
  220. Returns:
  221. 格式化后的文本
  222. """
  223. if format_type == "markdown":
  224. return f"**{text}**"
  225. elif format_type == "html":
  226. return f"<p>{text}</p>"
  227. else:
  228. return text
  229. # 7. 使用 ToolResult 的工具
  230. @tool()
  231. async def analyze_content(
  232. content_id: str,
  233. analysis_types: Optional[List[str]] = None,
  234. uid: str = ""
  235. ) -> ToolResult:
  236. """
  237. 分析内容(使用 ToolResult)
  238. ToolResult 支持双层记忆管理:
  239. - output: 完整结果(可能很长)
  240. - long_term_memory: 简短摘要(永久保存)
  241. Args:
  242. content_id: 要分析的内容ID
  243. analysis_types: 分析类型列表(sentiment/keywords/summary)
  244. uid: 用户ID(自动注入)
  245. Returns:
  246. ToolResult 包含分析结果
  247. """
  248. types = analysis_types or ["sentiment", "keywords"]
  249. result = {
  250. "content_id": content_id,
  251. "analyses": {}
  252. }
  253. if "sentiment" in types:
  254. result["analyses"]["sentiment"] = {
  255. "score": 0.8,
  256. "label": "positive",
  257. "confidence": 0.92
  258. }
  259. if "keywords" in types:
  260. result["analyses"]["keywords"] = [
  261. {"word": "AI", "weight": 0.9},
  262. {"word": "学习", "weight": 0.7}
  263. ]
  264. return ToolResult(
  265. title=f"Analysis of {content_id}",
  266. output=json.dumps(result, indent=2, ensure_ascii=False),
  267. long_term_memory=f"Analyzed {content_id}: {', '.join(types)}",
  268. metadata={"types": types}
  269. )
  270. # ============================================================
  271. # 高级功能示例
  272. # ============================================================
  273. # 8. 域名过滤示例
  274. @tool(url_patterns=["*.google.com", "www.google.*"])
  275. async def google_search(query: str, uid: str = "") -> ToolResult:
  276. """
  277. Google 搜索(仅在 Google 页面可用)
  278. 使用 url_patterns 限制工具只在特定域名显示。
  279. 在 Google 页面时,此工具会出现在可用工具列表中。
  280. 在其他页面时,此工具会被过滤掉。
  281. Args:
  282. query: 搜索查询
  283. uid: 用户ID(自动注入)
  284. Returns:
  285. 搜索结果
  286. """
  287. return ToolResult(
  288. title="Google Search",
  289. output=f"Searching Google for: {query}",
  290. long_term_memory=f"Searched Google for '{query}'"
  291. )
  292. @tool(url_patterns=["*.github.com"])
  293. async def create_github_issue(
  294. title: str,
  295. body: str,
  296. uid: str = ""
  297. ) -> ToolResult:
  298. """
  299. 创建 GitHub Issue(仅在 GitHub 页面可用)
  300. Args:
  301. title: Issue 标题
  302. body: Issue 内容
  303. uid: 用户ID(自动注入)
  304. Returns:
  305. 创建结果
  306. """
  307. return ToolResult(
  308. title="Issue Created",
  309. output=f"Created issue: {title}",
  310. long_term_memory=f"Created GitHub issue: {title}"
  311. )
  312. @tool() # 无 url_patterns,所有页面都可用
  313. async def take_screenshot(uid: str = "") -> ToolResult:
  314. """截图(所有页面都可用)"""
  315. return ToolResult(
  316. title="Screenshot",
  317. output="Screenshot taken",
  318. attachments=["screenshot_001.png"]
  319. )
  320. # 9. 敏感数据处理示例
  321. @tool(url_patterns=["*.github.com"])
  322. async def github_login(
  323. username: str,
  324. password: str,
  325. totp_code: str,
  326. uid: str = ""
  327. ) -> ToolResult:
  328. """
  329. GitHub 登录(支持敏感数据占位符)
  330. LLM 会输出类似:
  331. {
  332. "username": "user@example.com",
  333. "password": "<secret>github_password</secret>",
  334. "totp_code": "<secret>github_2fa_bu_2fa_code</secret>"
  335. }
  336. 执行时会自动替换为实际值。
  337. Args:
  338. username: 用户名
  339. password: 密码(可以是占位符)
  340. totp_code: TOTP 验证码(可以是占位符,自动生成)
  341. uid: 用户ID(自动注入)
  342. Returns:
  343. 登录结果
  344. """
  345. # 注意:password 和 totp_code 在到达这里时已经被替换
  346. return ToolResult(
  347. title="Login Successful",
  348. output=f"Logged in as {username}",
  349. long_term_memory=f"Logged in to GitHub as {username}"
  350. )
  351. # 10. 组合所有功能
  352. @tool(
  353. url_patterns=["*.example.com"],
  354. requires_confirmation=True,
  355. editable_params=["message"],
  356. display={
  357. "zh": {
  358. "name": "发送认证消息",
  359. "params": {
  360. "recipient": "接收者",
  361. "message": "消息内容",
  362. "api_key": "API密钥"
  363. }
  364. }
  365. }
  366. )
  367. async def send_authenticated_message(
  368. recipient: str,
  369. message: str,
  370. api_key: str,
  371. ctx: ToolContext,
  372. uid: str = ""
  373. ) -> ToolResult:
  374. """
  375. 发送消息(组合多个功能)
  376. 展示所有高级功能:
  377. - 仅在 example.com 可用(域名过滤)
  378. - 需要用户确认(危险操作)
  379. - 消息可编辑(用户微调)
  380. - API key 使用敏感数据占位符
  381. - 使用 ToolContext 获取上下文
  382. Args:
  383. recipient: 接收者
  384. message: 消息内容
  385. api_key: API密钥(可以是占位符)
  386. ctx: 工具上下文
  387. uid: 用户ID(自动注入)
  388. Returns:
  389. 发送结果
  390. """
  391. # api_key 会从 <secret>api_key</secret> 替换为实际值
  392. # ctx 包含 page_url, browser_session 等信息
  393. return ToolResult(
  394. title="Message Sent",
  395. output=f"Sent to {recipient}: {message}",
  396. long_term_memory=f"Sent message to {recipient} on {ctx.page_url}",
  397. metadata={"recipient": recipient}
  398. )
  399. # ============================================================
  400. # 使用示例
  401. # ============================================================
  402. async def main():
  403. registry = get_tool_registry()
  404. print("=" * 60)
  405. print("工具系统完整示例")
  406. print("=" * 60)
  407. # ============================================================
  408. # 示例 1:基础工具调用
  409. # ============================================================
  410. print("\n1. 基础工具调用")
  411. print("-" * 60)
  412. result = await registry.execute("hello_world", {"name": "Alice"})
  413. print(f"hello_world: {result}")
  414. result = await registry.execute("search_content", {"query": "Python", "limit": 5})
  415. print(f"search_content: {result}")
  416. # ============================================================
  417. # 示例 2:域名过滤
  418. # ============================================================
  419. print("\n\n2. 域名过滤示例")
  420. print("-" * 60)
  421. # 在 Google 页面
  422. google_url = "https://www.google.com/search?q=test"
  423. google_tools = registry.get_tool_names(google_url)
  424. print(f"在 {google_url} 可用的工具:")
  425. print(f" 包含 google_search: {'google_search' in google_tools}")
  426. # 在 GitHub 页面
  427. github_url = "https://github.com/user/repo"
  428. github_tools = registry.get_tool_names(github_url)
  429. print(f"\n在 {github_url} 可用的工具:")
  430. print(f" 包含 create_github_issue: {'create_github_issue' in github_tools}")
  431. print(f" 包含 google_search: {'google_search' in github_tools}")
  432. # ============================================================
  433. # 示例 3:敏感数据处理
  434. # ============================================================
  435. print("\n\n3. 敏感数据处理示例")
  436. print("-" * 60)
  437. # 配置敏感数据
  438. sensitive_data = {
  439. "*.github.com": {
  440. "github_password": "my_secret_password",
  441. "github_2fa_bu_2fa_code": "JBSWY3DPEHPK3PXP" # TOTP secret
  442. }
  443. }
  444. # 模拟 LLM 输出(包含占位符)
  445. llm_output_args = {
  446. "username": "user@example.com",
  447. "password": "<secret>github_password</secret>",
  448. "totp_code": "<secret>github_2fa_bu_2fa_code</secret>"
  449. }
  450. print("LLM 输出的参数(包含占位符):")
  451. print(f" {llm_output_args}")
  452. # 执行工具(自动替换敏感数据)
  453. result = await registry.execute(
  454. "github_login",
  455. llm_output_args,
  456. context={"page_url": "https://github.com/login"},
  457. sensitive_data=sensitive_data
  458. )
  459. print(f"\n执行结果(密码已替换):")
  460. print(f" {result}")
  461. # ============================================================
  462. # 示例 4:工具统计
  463. # ============================================================
  464. print("\n\n4. 工具统计示例")
  465. print("-" * 60)
  466. # 模拟多次调用
  467. for i in range(5):
  468. await registry.execute("google_search", {"query": f"test {i}"})
  469. await registry.execute("take_screenshot", {})
  470. await registry.execute("take_screenshot", {})
  471. # 查看统计
  472. stats = registry.get_stats()
  473. print("工具使用统计:")
  474. for tool_name, tool_stats in stats.items():
  475. if tool_stats["call_count"] > 0:
  476. print(f"\n {tool_name}:")
  477. print(f" 调用次数: {tool_stats['call_count']}")
  478. print(f" 成功率: {tool_stats['success_rate']:.1%}")
  479. print(f" 平均执行时间: {tool_stats['average_duration']:.3f}s")
  480. # 获取 Top 工具
  481. print("\n\nTop 3 最常用工具:")
  482. top_tools = registry.get_top_tools(limit=3, by="call_count")
  483. for i, tool_name in enumerate(top_tools, 1):
  484. tool_stats = stats[tool_name]
  485. print(f" {i}. {tool_name} ({tool_stats['call_count']} 次调用)")
  486. if __name__ == "__main__":
  487. asyncio.run(main())