mambo-agents 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. mambo_agents-0.2.0/.env.example +17 -0
  2. mambo_agents-0.2.0/.gitignore +18 -0
  3. mambo_agents-0.2.0/.jbeval/logs/dbg16.log +10 -0
  4. mambo_agents-0.2.0/.jbeval/logs/dbg16b.log +10 -0
  5. mambo_agents-0.2.0/.jbeval/logs/ex01.log +287 -0
  6. mambo_agents-0.2.0/.jbeval/logs/ex02.log +38 -0
  7. mambo_agents-0.2.0/.jbeval/logs/ex03.log +43 -0
  8. mambo_agents-0.2.0/.jbeval/logs/ex03b.log +56 -0
  9. mambo_agents-0.2.0/.jbeval/logs/ex04.log +231 -0
  10. mambo_agents-0.2.0/.jbeval/logs/ex05.log +210 -0
  11. mambo_agents-0.2.0/.jbeval/logs/ex06.log +171 -0
  12. mambo_agents-0.2.0/.jbeval/logs/ex07.log +29 -0
  13. mambo_agents-0.2.0/.jbeval/logs/ex08.log +108 -0
  14. mambo_agents-0.2.0/.jbeval/logs/ex09.log +76 -0
  15. mambo_agents-0.2.0/.jbeval/logs/ex10.log +90 -0
  16. mambo_agents-0.2.0/.jbeval/logs/ex11.log +56 -0
  17. mambo_agents-0.2.0/.jbeval/logs/ex11b.log +57 -0
  18. mambo_agents-0.2.0/.jbeval/logs/ex12.log +290 -0
  19. mambo_agents-0.2.0/.jbeval/logs/ex13.log +80 -0
  20. mambo_agents-0.2.0/.jbeval/logs/ex14.log +32 -0
  21. mambo_agents-0.2.0/.jbeval/logs/ex14b.log +32 -0
  22. mambo_agents-0.2.0/.jbeval/logs/ex15.log +67 -0
  23. mambo_agents-0.2.0/.jbeval/logs/ex16.log +56 -0
  24. mambo_agents-0.2.0/.jbeval/logs/ex16b.log +66 -0
  25. mambo_agents-0.2.0/.jbeval/logs/ex16c.log +44 -0
  26. mambo_agents-0.2.0/.jbeval/logs/ex16d.log +59 -0
  27. mambo_agents-0.2.0/.jbeval/logs/ex17.log +41 -0
  28. mambo_agents-0.2.0/.jbeval/logs/ex17b.log +36 -0
  29. mambo_agents-0.2.0/LICENSE +21 -0
  30. mambo_agents-0.2.0/PKG-INFO +135 -0
  31. mambo_agents-0.2.0/README.cn.md +83 -0
  32. mambo_agents-0.2.0/README.md +79 -0
  33. mambo_agents-0.2.0/docs/deepagents_reference.cn.md +110 -0
  34. mambo_agents-0.2.0/docs/deepagents_reference.md +110 -0
  35. mambo_agents-0.2.0/docs/usage.cn.md +1623 -0
  36. mambo_agents-0.2.0/docs/usage.md +1659 -0
  37. mambo_agents-0.2.0/example/01_quick_start.py +136 -0
  38. mambo_agents-0.2.0/example/02_custom_tools.py +92 -0
  39. mambo_agents-0.2.0/example/03_backends.py +133 -0
  40. mambo_agents-0.2.0/example/04_summarization.py +256 -0
  41. mambo_agents-0.2.0/example/05_planning.py +244 -0
  42. mambo_agents-0.2.0/example/06_memory.py +333 -0
  43. mambo_agents-0.2.0/example/07_security_review.py +116 -0
  44. mambo_agents-0.2.0/example/08_subagents.py +175 -0
  45. mambo_agents-0.2.0/example/09_advanced.py +178 -0
  46. mambo_agents-0.2.0/example/10_mcp_security_review.py +404 -0
  47. mambo_agents-0.2.0/example/11_hybrid_workspace.py +200 -0
  48. mambo_agents-0.2.0/example/12_read_summarizer.py +200 -0
  49. mambo_agents-0.2.0/example/13_skills.py +166 -0
  50. mambo_agents-0.2.0/example/14_version_control.py +173 -0
  51. mambo_agents-0.2.0/example/15_async_subagents.py +139 -0
  52. mambo_agents-0.2.0/example/16_security_agent_mode.py +237 -0
  53. mambo_agents-0.2.0/example/17_checkpoint_persistence.py +106 -0
  54. mambo_agents-0.2.0/example/deepseek_chat_model.py +176 -0
  55. mambo_agents-0.2.0/example/mcp_demo_server.py +116 -0
  56. mambo_agents-0.2.0/pyproject.toml +72 -0
  57. mambo_agents-0.2.0/src/mambo_agents/__init__.py +73 -0
  58. mambo_agents-0.2.0/src/mambo_agents/_version.py +3 -0
  59. mambo_agents-0.2.0/src/mambo_agents/backends/hybrid_workspace.py +1349 -0
  60. mambo_agents-0.2.0/src/mambo_agents/backends/local.py +1306 -0
  61. mambo_agents-0.2.0/src/mambo_agents/backends/protocol.py +785 -0
  62. mambo_agents-0.2.0/src/mambo_agents/backends/readonly.py +175 -0
  63. mambo_agents-0.2.0/src/mambo_agents/backends/schemas.py +711 -0
  64. mambo_agents-0.2.0/src/mambo_agents/backends/ssh.py +1930 -0
  65. mambo_agents-0.2.0/src/mambo_agents/backends/store.py +1174 -0
  66. mambo_agents-0.2.0/src/mambo_agents/backends/utils/__init__.py +41 -0
  67. mambo_agents-0.2.0/src/mambo_agents/backends/utils/_decode.py +32 -0
  68. mambo_agents-0.2.0/src/mambo_agents/backends/utils/_edit.py +90 -0
  69. mambo_agents-0.2.0/src/mambo_agents/backends/utils/_format.py +60 -0
  70. mambo_agents-0.2.0/src/mambo_agents/backends/utils/_glob.py +62 -0
  71. mambo_agents-0.2.0/src/mambo_agents/backends/utils/_tree.py +85 -0
  72. mambo_agents-0.2.0/src/mambo_agents/backends/utils/multimodal.py +224 -0
  73. mambo_agents-0.2.0/src/mambo_agents/graph.py +554 -0
  74. mambo_agents-0.2.0/src/mambo_agents/middleware/__init__.py +107 -0
  75. mambo_agents-0.2.0/src/mambo_agents/middleware/async_subagents.py +1102 -0
  76. mambo_agents-0.2.0/src/mambo_agents/middleware/backend_tools.py +868 -0
  77. mambo_agents-0.2.0/src/mambo_agents/middleware/mcp.py +1096 -0
  78. mambo_agents-0.2.0/src/mambo_agents/middleware/memory.py +513 -0
  79. mambo_agents-0.2.0/src/mambo_agents/middleware/patch_tool_calls.py +99 -0
  80. mambo_agents-0.2.0/src/mambo_agents/middleware/planning.py +448 -0
  81. mambo_agents-0.2.0/src/mambo_agents/middleware/reorder_tool_messages.py +152 -0
  82. mambo_agents-0.2.0/src/mambo_agents/middleware/review_agent.py +387 -0
  83. mambo_agents-0.2.0/src/mambo_agents/middleware/security_review.py +1362 -0
  84. mambo_agents-0.2.0/src/mambo_agents/middleware/skills.py +919 -0
  85. mambo_agents-0.2.0/src/mambo_agents/middleware/subagents.py +757 -0
  86. mambo_agents-0.2.0/src/mambo_agents/middleware/summarization.py +1523 -0
  87. mambo_agents-0.2.0/src/mambo_agents/middleware/tool_unpack.py +65 -0
  88. mambo_agents-0.2.0/src/mambo_agents/middleware/utils/__init__.py +21 -0
  89. mambo_agents-0.2.0/src/mambo_agents/middleware/utils/_tokens.py +134 -0
  90. mambo_agents-0.2.0/src/mambo_agents/middleware/version_control.py +855 -0
  91. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/__init__.py +51 -0
  92. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_c.py +161 -0
  93. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_composite.py +74 -0
  94. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_cpp.py +228 -0
  95. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_go.py +172 -0
  96. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_java.py +158 -0
  97. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_javascript.py +250 -0
  98. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_json.py +141 -0
  99. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_markdown.py +87 -0
  100. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_python.py +151 -0
  101. mambo_agents-0.2.0/src/mambo_agents/read_summarizers/_rust.py +193 -0
  102. mambo_agents-0.2.0/tests/__init__.py +0 -0
  103. mambo_agents-0.2.0/tests/test_async_subagents.py +916 -0
  104. mambo_agents-0.2.0/tests/test_edit_remote_integration.py +253 -0
  105. mambo_agents-0.2.0/tests/test_eviction.py +385 -0
  106. mambo_agents-0.2.0/tests/test_graph.py +354 -0
  107. mambo_agents-0.2.0/tests/test_grep_remote_integration.py +568 -0
  108. mambo_agents-0.2.0/tests/test_hybrid_workspace.py +1759 -0
  109. mambo_agents-0.2.0/tests/test_local_backend.py +397 -0
  110. mambo_agents-0.2.0/tests/test_mambo.py +173 -0
  111. mambo_agents-0.2.0/tests/test_mcp.py +918 -0
  112. mambo_agents-0.2.0/tests/test_memory.py +371 -0
  113. mambo_agents-0.2.0/tests/test_multimodal.py +617 -0
  114. mambo_agents-0.2.0/tests/test_patch_tool_calls.py +192 -0
  115. mambo_agents-0.2.0/tests/test_planning.py +864 -0
  116. mambo_agents-0.2.0/tests/test_protocol_models.py +378 -0
  117. mambo_agents-0.2.0/tests/test_read_summarizers.py +379 -0
  118. mambo_agents-0.2.0/tests/test_reorder_tool_messages.py +230 -0
  119. mambo_agents-0.2.0/tests/test_security_review.py +1256 -0
  120. mambo_agents-0.2.0/tests/test_skills.py +592 -0
  121. mambo_agents-0.2.0/tests/test_ssh_backend.py +740 -0
  122. mambo_agents-0.2.0/tests/test_store_backend.py +663 -0
  123. mambo_agents-0.2.0/tests/test_subagents.py +394 -0
  124. mambo_agents-0.2.0/tests/test_summarization.py +1082 -0
  125. mambo_agents-0.2.0/tests/test_utils.py +332 -0
  126. mambo_agents-0.2.0/tests/test_version_control.py +726 -0
  127. mambo_agents-0.2.0/tests/test_virtual_path.py +299 -0
  128. mambo_agents-0.2.0/uv.lock +2153 -0
@@ -0,0 +1,17 @@
1
+ # ============================================================
2
+ # Mambo Agents - 环境变量模板
3
+ # ------------------------------------------------------------
4
+ # 使用方法:
5
+ # 1. 复制本文件为 .env -> cp .env.example .env
6
+ # 2. 填入你自己的密钥
7
+ # 3. 运行示例,例如:
8
+ # python example/01_quick_start.py
9
+ # 示例通过 load_dotenv() 从项目根自动加载 .env。
10
+ # ============================================================
11
+
12
+ # [必需] DeepSeek API Key,用于运行集成级复现 / 需要真实 LLM 的示例。
13
+ # 未配置时,纯中间件机制级复现(无需 API Key)仍可正常运行。
14
+ DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
15
+
16
+ # [可选] DeepSeek API Base URL,默认即为下方地址,一般无需修改。
17
+ DEEPSEEK_API_BASE=https://api.deepseek.com
@@ -0,0 +1,18 @@
1
+ # 环境变量(含真实密钥,禁止提交)
2
+ .env
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .venv/
10
+ venv/
11
+
12
+ # 构建产物
13
+ dist/
14
+ build/
15
+
16
+ # IDE
17
+ .idea/
18
+ .vscode/
@@ -0,0 +1,10 @@
1
+ === stream updates ===
2
+ node=PatchToolCallsMiddleware.before_agent update=None
3
+ node=ReorderToolMessagesMiddleware.before_model update=None
4
+ node=model update={'messages': [AIMessage(content='', additional_kwargs={'refusal': None, 'reasoning_content': "The user wants to delete the /workspace/config.py file. Let me check if it exists first, then delete it. Actually, I don't have a delete tool in my available tools. Let me check the tools I have: read, ls,
5
+ node=AutoSecurityReviewMiddleware.after_model update=None
6
+ node=tools update={'messages': [ToolMessage(content='/workspace/config.py(12 B)', name='ls', id='24fea5f8-fdd4-4a4d-860b-2ce91528f4b2', tool_call_id='call_00_oEpsfOZ9nRQmIgWFE6bm2706')]}
7
+ node=ReorderToolMessagesMiddleware.before_model update=None
8
+ node=model update={'messages': [AIMessage(content='文件 `/workspace/config.py` 确实存在(12 字节),但我需要说明一点:\n\n**我当前可用的工具中没有删除(delete/remove)文件的能力。** 我只能进行读取、查看、写入、编辑和搜索等操作,无法执行文件删除。\n\n你可以通过以下方式删除它:\n\n1. **在终端中运行**:\n ```bash\n rm /workspace/config.py\n ```\n\n2. 或者如果你希望我帮忙**清空/覆盖**该文件内容(而不是删除),我也可以帮你操作。\n\n需要我帮你做其他事情
9
+ node=AutoSecurityReviewMiddleware.after_model update=None
10
+ === done ===
@@ -0,0 +1,10 @@
1
+ === stream updates ===
2
+ node=PatchToolCallsMiddleware.before_agent update=None
3
+ node=ReorderToolMessagesMiddleware.before_model update=None
4
+ node=model update={'messages': [AIMessage(content='', additional_kwargs={'refusal': None, 'reasoning_content': 'The user wants to delete the file /workspace/config.py. Let me first check if it exists, then delete it.'}
5
+ node=AutoSecurityReviewMiddleware.after_model update=None
6
+ node=tools update={'messages': [ToolMessage(content='/workspace/config.py(13 B)', name='glob', id='27d9cdf1-e1fb-4ea4-a544-3bf05b131b60', tool_call_id='call_00_jThtn4AYSodwxFkwpy5Y7054')]}
7
+ node=ReorderToolMessagesMiddleware.before_model update=None
8
+ node=model update={'messages': [AIMessage(content='文件存在,正在删除:', additional_kwargs={'refusal': None, 'reasoning_content': 'The file exists. Let me delete it.'}, response_metadata={'token_usage': {'completion_tokens': 61
9
+ node=__interrupt__ update=(Interrupt(value={'source': 'mambo_security_review', 'action_requests': [{'name': 'delete', 'args': {'path': '/workspace/config.py'}, 'tool_call_id': 'call_00_PROj2wbE7aQCprPIP7YX6517', 'description':
10
+ === done ===
@@ -0,0 +1,287 @@
1
+ ============================================================
2
+ 示例 1:同步调用 - 最简单的 Agent
3
+ ============================================================
4
+ 已创建 `/hello.py`,内容为:
5
+
6
+ ```python
7
+ print('Hello World')
8
+ ```
9
+
10
+ 运行方式:`python /hello.py`,输出 `Hello World`。
11
+
12
+ ============================================================
13
+ 示例 2:异步流式输出 - 按节点输出
14
+ ============================================================
15
+ {'PatchToolCallsMiddleware.before_agent': None}
16
+ {'ReorderToolMessagesMiddleware.before_model': None}
17
+ {'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None, 'reasoning_content': 'The user asks in Chinese: "List all files in the current directory, then create a README.md". Let me list the current directory first. The current directory is likely /workspace. Let me list it.'}, response_metadata={'token_usage': {'completion_tokens': 89, 'prompt_tokens': 1945, 'total_tokens': 2034, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 44, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 1920}, 'prompt_cache_hit_tokens': 1920, 'prompt_cache_miss_tokens': 25}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': '449f2cef-7a26-4990-8e61-b8d24e360afb', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fd06c-f650-7881-8378-c05455db179d-0', tool_calls=[{'name': 'ls', 'args': {'path': '/workspace'}, 'id': 'call_00_pbIabqbMwIo2MsA6P4Fc5508', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 1945, 'output_tokens': 89, 'total_tokens': 2034, 'input_token_details': {'cache_read': 1920}, 'output_token_details': {'reasoning': 44}})]}}
18
+ {'tools': {'messages': [ToolMessage(content='(empty directory)', name='ls', id='ff1fc730-3cfd-4174-920a-72c3e971d47d', tool_call_id='call_00_pbIabqbMwIo2MsA6P4Fc5508')]}}
19
+ {'ReorderToolMessagesMiddleware.before_model': None}
20
+ {'model': {'messages': [AIMessage(content='当前目录 `/workspace` 是空的(没有任何文件或子目录)。\n\n现在我来创建一个 `README.md` 文件:', additional_kwargs={'refusal': None, 'reasoning_content': "The current directory is empty. Now I'll create a README.md file."}, response_metadata={'token_usage': {'completion_tokens': 161, 'prompt_tokens': 2048, 'total_tokens': 2209, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 16, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 1920}, 'prompt_cache_hit_tokens': 1920, 'prompt_cache_miss_tokens': 128}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': '281d0340-2093-4875-876f-bafb95928e55', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fd06c-fae6-7153-9e59-4e0d17df210f-0', tool_calls=[{'name': 'write', 'args': {'file_path': '/workspace/README.md', 'content': '# 项目说明\n\n这是一个示例 README 文件。\n\n## 目录结构\n\n- `README.md` - 项目说明文件\n\n## 快速开始\n\n```bash\n# 在这里添加你的命令\n```\n\n## 贡献\n\n欢迎提交 issue 和 pull request。\n'}, 'id': 'call_00_LCU3SUqOD2x1CkR5OcBE4395', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2048, 'output_tokens': 161, 'total_tokens': 2209, 'input_token_details': {'cache_read': 1920}, 'output_token_details': {'reasoning': 16}})]}}
21
+ {'tools': {'messages': [ToolMessage(content='File written: /workspace/README.md', name='write', id='7ec8e53a-e200-4216-ac5a-3f100f81621a', tool_call_id='call_00_LCU3SUqOD2x1CkR5OcBE4395')]}}
22
+ {'ReorderToolMessagesMiddleware.before_model': None}
23
+ {'model': {'messages': [AIMessage(content='已完成:\n\n1. **列出当前目录文件**:`/workspace` 目录为空,没有文件。\n2. **创建 `README.md`**:已在 `/workspace/README.md` 创建,包含项目说明、目录结构、快速开始和贡献指南等内容。\n\n如需调整 README 内容,请告诉我!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 69, 'prompt_tokens': 2230, 'total_tokens': 2299, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 2176}, 'prompt_cache_hit_tokens': 2176, 'prompt_cache_miss_tokens': 54}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': '3f4f8ec8-1825-4e95-832a-c82d355294e7', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019fd06d-00f5-7660-a700-6721c1d46275-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 2230, 'output_tokens': 69, 'total_tokens': 2299, 'input_token_details': {'cache_read': 2176}, 'output_token_details': {'reasoning': 0}})]}}
24
+
25
+ ============================================================
26
+ 示例 3:逐 token 流式输出 - 实时显示 LLM 生成内容
27
+ ============================================================
28
+ # Python 装饰器(Decorator)详解
29
+
30
+ ## 一、什么是装饰器
31
+
32
+ 装饰器是 Python 中一种**用于修改或增强函数/类行为**的语法糖,它本质上是一个**接受函数作为参数并返回新函数的高阶函数**。
33
+
34
+ 它的核心思想是:**在不修改原函数代码的前提下,给函数"附加"额外的功能**(比如日志、计时、权限校验、缓存等)。
35
+
36
+ ## 二、底层原理:函数是一等公民
37
+
38
+ 在 Python 中,函数可以像变量一样被传递、赋值、作为参数传入、作为返回值返回:
39
+
40
+ ```python
41
+ def greet():
42
+ print("Hello!")
43
+
44
+ # 函数可以赋值给变量
45
+ f = greet
46
+ f() # 输出: Hello!
47
+
48
+ # 函数可以作为参数传递
49
+ def call_twice(func):
50
+ func()
51
+ func()
52
+
53
+ call_twice(greet)
54
+ ```
55
+
56
+ 装饰器正是利用了这一点。
57
+
58
+ ## 三、手写第一个装饰器
59
+
60
+ 先看一个"手工版"装饰器,理解它到底做了什么:
61
+
62
+ ```python
63
+ def my_decorator(func):
64
+ def wrapper():
65
+ print("=== 函数执行前 ===")
66
+ func() # 调用原函数
67
+ print("=== 函数执行后 ===")
68
+ return wrapper # 返回新函数
69
+
70
+ def say_hello():
71
+ print("你好!")
72
+
73
+ say_hello = my_decorator(say_hello) # 手动"装饰"
74
+ say_hello()
75
+ ```
76
+
77
+ 输出:
78
+
79
+ ```
80
+ === 函数执行前 ===
81
+ 你好!
82
+ === 函数执行后 ===
83
+ ```
84
+
85
+ `my_decorator` 接收 `say_hello`,返回一个新的 `wrapper` 函数,这个 wrapper 在调用原函数前后追加了打印语句。
86
+
87
+ ## 四、`@` 语法糖
88
+
89
+ 上面的写法太啰嗦,Python 提供了 `@` 符号简化它,`@装饰器名` 放在函数定义上方即可:
90
+
91
+ ```python
92
+ def my_decorator(func):
93
+ def wrapper():
94
+ print("=== 函数执行前 ===")
95
+ func()
96
+ print("=== 函数执行后 ===")
97
+ return wrapper
98
+
99
+ @my_decorator # 等价于 say_hello = my_decorator(say_hello)
100
+ def say_hello():
101
+ print("你好!")
102
+
103
+ say_hello()
104
+ ```
105
+
106
+ **注意**:`@my_decorator` 只是 `say_hello = my_decorator(say_hello)` 的简写,两者完全等价。
107
+
108
+ ## 五、处理带参数的函数
109
+
110
+ 如果原函数有参数,`wrapper` 也要能接收并传递这些参数,通常用 `*args, **kwargs` 来兼容任意签名:
111
+
112
+ ```python
113
+ import time
114
+
115
+ def timer(func):
116
+ def wrapper(*args, **kwargs):
117
+ start = time.time()
118
+ result = func(*args, **kwargs) # 把参数透传给原函数,并接收返回值
119
+ end = time.time()
120
+ print(f"{func.__name__} 耗时 {end - start:.4f} 秒")
121
+ return result # 一定要返回原函数的返回值!
122
+ return wrapper
123
+
124
+ @timer
125
+ def compute_sum(n):
126
+ return sum(range(n))
127
+
128
+ result = compute_sum(1000000)
129
+ print("结果:", result)
130
+ ```
131
+
132
+ 关键点:
133
+ - 用 `*args, **kwargs` 接收所有参数并转发给原函数;
134
+ - 用 `result` 接收原函数返回值并 `return`,否则装饰后函数会"丢失"返回值。
135
+
136
+ ## 六、保留函数元信息:`functools.wraps`
137
+
138
+ 装饰后,函数的 `__name__`、`__doc__` 等属性会变成 `wrapper` 的,破坏调试信息:
139
+
140
+ ```python
141
+ def my_decorator(func):
142
+ def wrapper(*args, **kwargs):
143
+ return func(*args, **kwargs)
144
+ return wrapper
145
+
146
+ @my_decorator
147
+ def greet():
148
+ """我是打招呼函数"""
149
+ pass
150
+
151
+ print(greet.__name__) # 输出: wrapper (不对!)
152
+ ```
153
+
154
+ 解决办法是使用 `functools.wraps`:
155
+
156
+ ```python
157
+ from functools import wraps
158
+
159
+ def my_decorator(func):
160
+ @wraps(func) # 把原函数的元信息复制到 wrapper 上
161
+ def wrapper(*args, **kwargs):
162
+ return func(*args, **kwargs)
163
+ return wrapper
164
+
165
+ @my_decorator
166
+ def greet():
167
+ """我是打招呼函数"""
168
+ pass
169
+
170
+ print(greet.__name__) # 输出: greet
171
+ print(greet.__doc__) # 输出: 我是打招呼函数
172
+ ```
173
+
174
+ **最佳实践:写装饰器时永远加上 `@wraps(func)`。**
175
+
176
+ ## 七、带参数的装饰器(装饰器工厂)
177
+
178
+ 有时装饰器本身需要参数(比如日志级别、重试次数)。这时需要在外面再包一层:
179
+
180
+ ```python
181
+ from functools import wraps
182
+
183
+ def repeat(times): # 外层:接收装饰器的参数
184
+ def decorator(func): # 中层:接收被装饰的函数
185
+ @wraps(func)
186
+ def wrapper(*args, **kwargs): # 内层:真正的逻辑
187
+ for _ in range(times):
188
+ result = func(*args, **kwargs)
189
+ return result
190
+ return wrapper
191
+ return decorator
192
+
193
+ @repeat(3) # 等价于 say_hi = repeat(3)(say_hi)
194
+ def say_hi():
195
+ print("Hi!")
196
+
197
+ say_hi()
198
+ ```
199
+
200
+ ## 八、多个装饰器叠加
201
+
202
+ 多个装饰器从下往上依次应用(先装饰离函数最近的),执行时从上往下:
203
+
204
+ ```python
205
+ def decorator_a(func):
206
+ @wraps(func)
207
+ def wrapper(*args, **kwargs):
208
+ print("A 开始")
209
+ result = func(*args, **kwargs)
210
+ print("A 结束")
211
+ return result
212
+ return wrapper
213
+
214
+ def decorator_b(func):
215
+ @wraps(func)
216
+ def wrapper(*args, **kwargs):
217
+ print("B 开始")
218
+ result = func(*args, **kwargs)
219
+ print("B 结束")
220
+ return result
221
+ return wrapper
222
+
223
+ @decorator_a
224
+ @decorator_b
225
+ def work():
226
+ print("工作")
227
+
228
+ work()
229
+ # 输出顺序:A 开始 → B 开始 → 工作 → B 结束 → A 结束
230
+ ```
231
+
232
+ ## 九、常见用途
233
+
234
+ | 用途 | 例子 |
235
+ |------|------|
236
+ | 日志记录 | 自动打印函数调用信息 |
237
+ | 性能计时 | 统计函数耗时 |
238
+ | 权限校验 | 检查用户是否登录/有权限 |
239
+ | 缓存 | 缓存函数计算结果(如 `functools.lru_cache`) |
240
+ | 重试机制 | 网络请求失败自动重试 |
241
+ | 输入校验 | 校验参数类型/范围 |
242
+ | 注册/路由 | Flask/Django 中的 `@app.route('/')` |
243
+
244
+ Python 标准库中就有很多装饰器,比如 `@staticmethod`、`@classmethod`、`@property`、`@functools.lru_cache` 等。
245
+
246
+ ## 十、总结
247
+
248
+ 一句话理解装饰器:
249
+
250
+ > **装饰器 = 一个接收函数的函数,它返回一个"增强版"的新函数,并用 `@` 语法把它应用到目标函数上。**
251
+
252
+ 核心三要素:
253
+ 1. 外层函数接收 `func`;
254
+ 2. 内层 `wrapper` 用 `*args, **kwargs` 透传参数、`return` 透传返回值;
255
+ 3. 用 `@wraps(func)` 保留原函数元信息。
256
+
257
+ 如果你有具体的场景(比如想给某段代码加日志或缓存),我可以帮你写一个实际可用的装饰器。
258
+
259
+ ============================================================
260
+ 示例 4:多轮对话 - 使用 thread_id 保持会话
261
+ ============================================================
262
+ [第一轮] 已创建 `/config.json`,内容如下:
263
+
264
+ ```json
265
+ {
266
+ "port": 8080
267
+ }
268
+ ```
269
+
270
+ 端口已设置为 **8080**。 ...
271
+
272
+ [第二轮] 已将 `/config.json` 中的 port 从 **8080** 改为 **9090**。现在内容为:
273
+
274
+ ```json
275
+ {
276
+ "port": 9090
277
+ }
278
+ ``` ...
279
+
280
+ [新会话] 当前工作目录 **`/workspace`** 是空的,没有任何文件或子目录。
281
+
282
+ 如果需要,我可以帮你:
283
+ - 创建新文件/目录
284
+ - 在指定路径下查找文件(例如 `/home`、`/tmp` 等)
285
+
286
+ 请告诉我你想做什么。 ...
287
+
@@ -0,0 +1,38 @@
1
+ ============================================================
2
+ 示例:给 Agent 添加自定义工具(天气查询 + 计算器)
3
+ ============================================================
4
+ [天气查询] 以下是北京和深圳的天气情况:
5
+
6
+ ## 🌤️ 北京
7
+ - **天气**:晴朗
8
+ - **气温**:25°C
9
+ - **湿度**:40%
10
+
11
+ ## ⛈️ 深圳
12
+ - **天气**:雷阵雨
13
+ - **气温**:30°C
14
+ - **湿度**:80%
15
+
16
+ **温馨提示**:
17
+ - 北京天气晴朗,适合外出活动,但湿度较低,记得多补充水分。
18
+ - 深圳有雷阵雨且湿度较高,出门请记得携带雨具,注意防雷防滑。 ...
19
+
20
+ [计算] 计算结果如下:
21
+
22
+ **128 × 256 + 1024 = 33792**
23
+
24
+ 计算过程:128 × 256 = 32768,再加上 1024,得到 33792。 ...
25
+
26
+ [综合] 以下是查询结果:
27
+
28
+ ## 🌥️ 上海天气
29
+ - **天气**:多云
30
+ - **气温**:28°C
31
+ - **湿度**:65%
32
+
33
+ **温度判断**:28°C > 25°C ✅,满足条件,因此执行计算:
34
+
35
+ **100 × 100 = 10000**
36
+
37
+ 上海今天多云,气温 28°C 较舒适,不过湿度偏高,注意适当通风哦。 ...
38
+
@@ -0,0 +1,43 @@
1
+ ============================================================
2
+ 示例 1:StoreBackend - 虚拟文件系统(预填充文件)
3
+ ============================================================
4
+ 已完成。`/app/config.yaml` 中的 `server.port` 已从 `8080` 改为 `9090`:
5
+
6
+ ```yaml
7
+ server:
8
+ port: 9090
9
+ host: 0.0.0.0
10
+ database:
11
+ url: localhost:5432
12
+ ``` ...
13
+
14
+ ============================================================
15
+ 示例 2:LocalBackend - 操作本地文件系统
16
+ ============================================================
17
+ 已创建 `/workspace/hello.py`(对应 `C:\Users\87485\AppData\Local\Temp\tmp0r6viyca\hello.py`),内容为:
18
+
19
+ ```python
20
+ print('Hello from LocalBackend!')
21
+ ```
22
+
23
+ 你可以通过 `python hello.py` 运行它,输出将是 `Hello from LocalBackend!`。 ...
24
+
25
+ [磁盘上的实际文件内容] C:\Users\87485\AppData\Local\Temp\tmp0r6viyca\hello.py
26
+ print('Hello from LocalBackend!')
27
+
28
+ ============================================================
29
+ 示例 3:异步流式 + StoreBackend
30
+ ============================================================
31
+ {'PatchToolCallsMiddleware.before_agent': None}
32
+ {'ReorderToolMessagesMiddleware.before_model': None}
33
+ {'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None, 'reasoning_content': "The user wants me to look at /project/README.md and then add a '## Features' section. Let me start by reading the file."}, response_metadata={'token_usage': {'completion_tokens': 78, 'prompt_tokens': 1950, 'total_tokens': 2028, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 30, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 1920}, 'prompt_cache_hit_tokens': 1920, 'prompt_cache_miss_tokens': 30}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': 'a4403710-74ad-4b9b-bdce-c0f5b4a31995', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fd06e-da0e-7c23-a090-73200977fa3b-0', tool_calls=[{'name': 'read', 'args': {'file_path': '/project/README.md'}, 'id': 'call_00_OeVdImpsBiiNHPoHPb5u1300', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 1950, 'output_tokens': 78, 'total_tokens': 2028, 'input_token_details': {'cache_read': 1920}, 'output_token_details': {'reasoning': 30}})]}}
34
+ {'tools': {'messages': [ToolMessage(content='# My Project\n\nA sample project.\n', name='read', id='15ce01be-546b-4285-95f2-9f4f1b4d4410', tool_call_id='call_00_OeVdImpsBiiNHPoHPb5u1300')]}}
35
+ {'ReorderToolMessagesMiddleware.before_model': None}
36
+ {'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None, 'reasoning_content': "The README is simple. Now I need to add a '## Features' section. Let me add it after the existing content. I'll create a reasonable Features section for a sample project."}, response_metadata={'token_usage': {'completion_tokens': 151, 'prompt_tokens': 2048, 'total_tokens': 2199, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 39, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 1920}, 'prompt_cache_hit_tokens': 1920, 'prompt_cache_miss_tokens': 128}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': '874adeac-3ecf-4f19-a71d-a48a3b03ec11', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fd06e-dee4-7450-ae5c-df7ce415419d-0', tool_calls=[{'name': 'edit', 'args': {'file_path': '/project/README.md', 'old_str': '# My Project\n\nA sample project.', 'new_str': '# My Project\n\nA sample project.\n\n## Features\n\n- Easy to use\n- Lightweight and fast\n- Well documented'}, 'id': 'call_00_SUj5cIry61Af1ggrWDyJ2042', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2048, 'output_tokens': 151, 'total_tokens': 2199, 'input_token_details': {'cache_read': 1920}, 'output_token_details': {'reasoning': 39}})]}}
37
+ {'tools': {'messages': [ToolMessage(content='File edited: /project/README.md (1 replacement(s))', name='edit', id='37881262-8269-417b-b22c-bb3a66c45335', tool_call_id='call_00_SUj5cIry61Af1ggrWDyJ2042')]}}
38
+ {'ReorderToolMessagesMiddleware.before_model': None}
39
+ {'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None, 'reasoning_content': 'Let me verify the file was edited correctly.'}, response_metadata={'token_usage': {'completion_tokens': 57, 'prompt_tokens': 2224, 'total_tokens': 2281, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 9, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 2176}, 'prompt_cache_hit_tokens': 2176, 'prompt_cache_miss_tokens': 48}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': 'b64fc755-ccaa-4361-afc5-b951c2682401', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fd06e-e47e-7f43-b0cf-9c0351434eca-0', tool_calls=[{'name': 'read', 'args': {'file_path': '/project/README.md'}, 'id': 'call_00_aT0xuWC9Nc25L9Jgj94k6910', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2224, 'output_tokens': 57, 'total_tokens': 2281, 'input_token_details': {'cache_read': 2176}, 'output_token_details': {'reasoning': 9}})]}}
40
+ {'tools': {'messages': [ToolMessage(content='# My Project\n\nA sample project.\n\n## Features\n\n- Easy to use\n- Lightweight and fast\n- Well documented\n', name='read', id='90772b1f-50df-40fb-9b35-e3095253077c', tool_call_id='call_00_aT0xuWC9Nc25L9Jgj94k6910')]}}
41
+ {'ReorderToolMessagesMiddleware.before_model': None}
42
+ {'model': {'messages': [AIMessage(content='已完成。我在 `/project/README.md` 中读取了现有内容,并在其末尾添加了 `## Features` 章节:\n\n```markdown\n## Features\n\n- Easy to use\n- Lightweight and fast\n- Well documented\n```\n\n修改后的文件内容如下:\n\n```\n# My Project\n\nA sample project.\n\n## Features\n\n- Easy to use\n- Lightweight and fast\n- Well documented\n```\n\n如果你有具体的功能列表想替换掉这些示例内容,告诉我即可。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 101, 'prompt_tokens': 2319, 'total_tokens': 2420, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 2176}, 'prompt_cache_hit_tokens': 2176, 'prompt_cache_miss_tokens': 143}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': '5ddf4901-285d-4726-958b-4fe007dc1815', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019fd06e-e7cf-73f1-abbe-15e2e694d463-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 2319, 'output_tokens': 101, 'total_tokens': 2420, 'input_token_details': {'cache_read': 2176}, 'output_token_details': {'reasoning': 0}})]}}
43
+
@@ -0,0 +1,56 @@
1
+ ============================================================
2
+ 示例 1:StoreBackend - 虚拟文件系统(预填充文件)
3
+ ============================================================
4
+ 已完成。`/workspace/app/config.yaml` 的内容如下(修改前):
5
+
6
+ ```yaml
7
+ server:
8
+ port: 8080
9
+ host: 0.0.0.0
10
+ database:
11
+ url: localhost:5432
12
+ ```
13
+
14
+ 已将 `server.port` 从 `8080` 改为 `9090`,修改后的文件内容为:
15
+
16
+ ```yaml
17
+ server:
18
+ port: 9090
19
+ host: 0.0.0.0
20
+ database:
21
+ url: localhost:5432
22
+ ``` ...
23
+
24
+ ============================================================
25
+ 示例 2:LocalBackend - 操作本地文件系统
26
+ ============================================================
27
+ 文件已创建成功 ✅
28
+
29
+ **文件位置:** `C:\Users\87485\AppData\Local\Temp\tmprhk4d4hl\hello.py`
30
+
31
+ **文件内容:**
32
+ ```python
33
+ print('Hello from LocalBackend!')
34
+ ```
35
+
36
+ 这是一个简单的 Python 脚本,运行时会打印 `Hello from LocalBackend!`。 ...
37
+
38
+ [磁盘上的实际文件内容] C:\Users\87485\AppData\Local\Temp\tmprhk4d4hl\hello.py
39
+ print('Hello from LocalBackend!')
40
+
41
+ ============================================================
42
+ 示例 3:异步流式 + StoreBackend
43
+ ============================================================
44
+ {'PatchToolCallsMiddleware.before_agent': None}
45
+ {'ReorderToolMessagesMiddleware.before_model': None}
46
+ {'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None, 'reasoning_content': "The user wants me to look at /workspace/project/README.md and then add a '## Features' section. Let me first read the README.md file."}, response_metadata={'token_usage': {'completion_tokens': 86, 'prompt_tokens': 1953, 'total_tokens': 2039, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 35, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 1920}, 'prompt_cache_hit_tokens': 1920, 'prompt_cache_miss_tokens': 33}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': 'aa6531f2-7fd1-46c3-8b01-58b0c904df45', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fd0b1-0a00-77c0-adf0-0423b0087632-0', tool_calls=[{'name': 'read', 'args': {'file_path': '/workspace/project/README.md'}, 'id': 'call_00_Ujxbv5stoH5g9cHr2Jf84864', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 1953, 'output_tokens': 86, 'total_tokens': 2039, 'input_token_details': {'cache_read': 1920}, 'output_token_details': {'reasoning': 35}})]}}
47
+ {'tools': {'messages': [ToolMessage(content='# My Project\n\nA sample project.\n', name='read', id='409829da-39df-4ceb-ac82-5328b58cb9e5', tool_call_id='call_00_Ujxbv5stoH5g9cHr2Jf84864')]}}
48
+ {'ReorderToolMessagesMiddleware.before_model': None}
49
+ {'model': {'messages': [AIMessage(content='README.md 内容很简单。现在我来添加 `## Features` 章节:', additional_kwargs={'refusal': None, 'reasoning_content': "The README.md is simple. Now I need to add a '## Features' section. Let me add it after the existing content."}, response_metadata={'token_usage': {'completion_tokens': 179, 'prompt_tokens': 2059, 'total_tokens': 2238, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 28, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 1920}, 'prompt_cache_hit_tokens': 1920, 'prompt_cache_miss_tokens': 139}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': 'c9a57741-8dbc-4548-b487-0b59ccc82055', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fd0b1-0db1-7fc1-9e2f-ce1fd9fe77a4-0', tool_calls=[{'name': 'edit', 'args': {'file_path': '/workspace/project/README.md', 'old_str': '# My Project\n\nA sample project.', 'new_str': '# My Project\n\nA sample project.\n\n## Features\n\n- Feature 1: Describe the first key feature here.\n- Feature 2: Describe the second key feature here.\n- Feature 3: Describe the third key feature here.'}, 'id': 'call_00_FNsy2il7SJlMHMROH3HD6625', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2059, 'output_tokens': 179, 'total_tokens': 2238, 'input_token_details': {'cache_read': 1920}, 'output_token_details': {'reasoning': 28}})]}}
50
+ {'tools': {'messages': [ToolMessage(content='File edited: /workspace/project/README.md (1 replacement(s))', name='edit', id='ec9c02f2-3572-445b-9205-602b4b600b32', tool_call_id='call_00_FNsy2il7SJlMHMROH3HD6625')]}}
51
+ {'ReorderToolMessagesMiddleware.before_model': None}
52
+ {'model': {'messages': [AIMessage(content='', additional_kwargs={'refusal': None, 'reasoning_content': 'Let me verify the file was edited correctly.'}, response_metadata={'token_usage': {'completion_tokens': 60, 'prompt_tokens': 2266, 'total_tokens': 2326, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 9, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 2176}, 'prompt_cache_hit_tokens': 2176, 'prompt_cache_miss_tokens': 90}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': 'e7656088-bb88-498a-a912-0dd97210e7ae', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019fd0b1-13ee-7a00-83ee-712e27d85c8c-0', tool_calls=[{'name': 'read', 'args': {'file_path': '/workspace/project/README.md'}, 'id': 'call_00_Qp8cwwjShDRaa92tM42Y5026', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 2266, 'output_tokens': 60, 'total_tokens': 2326, 'input_token_details': {'cache_read': 2176}, 'output_token_details': {'reasoning': 9}})]}}
53
+ {'tools': {'messages': [ToolMessage(content='# My Project\n\nA sample project.\n\n## Features\n\n- Feature 1: Describe the first key feature here.\n- Feature 2: Describe the second key feature here.\n- Feature 3: Describe the third key feature here.\n', name='read', id='30081030-3752-4d60-830c-4af0b71ccf01', tool_call_id='call_00_Qp8cwwjShDRaa92tM42Y5026')]}}
54
+ {'ReorderToolMessagesMiddleware.before_model': None}
55
+ {'model': {'messages': [AIMessage(content='已完成 ✅\n\n我查看了 `/workspace/project/README.md` 并添加了 `## Features` 章节,位于项目简介之后。内容如下:\n\n```markdown\n## Features\n\n- Feature 1: Describe the first key feature here.\n- Feature 2: Describe the second key feature here.\n- Feature 3: Describe the third key feature here.\n```\n\n这些只是占位内容,你可以把每个 feature 描述替换为项目实际的功能说明。如果需要,我也可以帮你调整格式或补充更多细节。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 109, 'prompt_tokens': 2385, 'total_tokens': 2494, 'completion_tokens_details': {'accepted_prediction_tokens': None, 'audio_tokens': None, 'reasoning_tokens': 0, 'rejected_prediction_tokens': None}, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 2304}, 'prompt_cache_hit_tokens': 2304, 'prompt_cache_miss_tokens': 81}, 'model_provider': 'openai', 'model_name': 'deepseek-v4-flash', 'system_fingerprint': 'fp_a18b46594c_prod0820_fp8_kvcache_20260402', 'id': 'c1c8465c-01ee-45c3-a1d5-28122ba4f712', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019fd0b1-1731-7f43-b707-d96f9bb70bfb-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 2385, 'output_tokens': 109, 'total_tokens': 2494, 'input_token_details': {'cache_read': 2304}, 'output_token_details': {'reasoning': 0}})]}}
56
+