ni.agentkit 0.6.2__tar.gz → 0.6.3__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 (115) hide show
  1. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/PKG-INFO +3 -3
  2. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/README.md +2 -2
  3. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/__init__.py +1 -1
  4. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/docs/QuickStart.md +49 -24
  5. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/docs/README.md +1 -1
  6. ni_agentkit-0.6.3/docs/TestReport.md +90 -0
  7. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/01_basic_chat.py +10 -9
  8. ni_agentkit-0.6.3/examples/ollama/05_human_in_the_loop.py +42 -0
  9. ni_agentkit-0.6.3/examples/ollama/08a_memory_simple_provider.py +59 -0
  10. ni_agentkit-0.6.3/examples/ollama/08b_memory_mem0_provider.py +57 -0
  11. ni_agentkit-0.6.3/examples/ollama/08c_memory_file_provider.py +102 -0
  12. ni_agentkit-0.6.3/examples/ollama/19_hitl_deterministic.py +57 -0
  13. ni_agentkit-0.6.3/examples/standard/08a_memory_simple_provider.py +59 -0
  14. ni_agentkit-0.6.3/examples/standard/08b_memory_mem0_provider.py +49 -0
  15. ni_agentkit-0.6.3/examples/standard/08c_memory_file_provider.py +102 -0
  16. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/ni.agentkit.egg-info/PKG-INFO +3 -3
  17. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/ni.agentkit.egg-info/SOURCES.txt +16 -0
  18. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/pyproject.toml +1 -1
  19. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/runner/events.py +32 -2
  20. ni_agentkit-0.6.2/docs/TestReport.md +0 -80
  21. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/LICENSE +0 -0
  22. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/_cli.py +0 -0
  23. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/agents/__init__.py +0 -0
  24. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/agents/agent.py +0 -0
  25. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/agents/base_agent.py +0 -0
  26. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/agents/orchestrators.py +0 -0
  27. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/docs/Architecture.md +0 -0
  28. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/docs/Reference.md +0 -0
  29. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/__init__.py +0 -0
  30. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/02_tool_calling.py +0 -0
  31. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/03_skill_usage.py +0 -0
  32. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/04_multi_agent.py +0 -0
  33. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/05_guardrail.py +0 -0
  34. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/06_orchestration.py +0 -0
  35. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/07_sync_async_stream.py +0 -0
  36. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/08_memory.py +0 -0
  37. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/09a_structured_data_sql.py +0 -0
  38. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/09b_structured_data_graph.py +0 -0
  39. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/10_skill_lifecycle.py +0 -0
  40. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/11_orchestration_enhancement.py +0 -0
  41. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/12_run_context_serialization.py +0 -0
  42. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/13_human_in_the_loop.py +0 -0
  43. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/14_event_standardization.py +0 -0
  44. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/15_multi_tenant_isolation.py +0 -0
  45. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/16_lifecycle_hooks.py +0 -0
  46. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/17_checkpoint_handoff_resume.py +0 -0
  47. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/18_model_cosplay.py +0 -0
  48. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/README.md +0 -0
  49. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/ollama/__init__.py +0 -0
  50. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/quickstart.py +0 -0
  51. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/01_basic_chat.py +0 -0
  52. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/02_tool_calling.py +0 -0
  53. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/03_skill_usage.py +0 -0
  54. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/04_multi_agent.py +0 -0
  55. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/05_guardrail.py +0 -0
  56. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/06_orchestration.py +0 -0
  57. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/07_sync_async_stream.py +0 -0
  58. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/08_memory.py +0 -0
  59. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/09a_structured_data_sql.py +0 -0
  60. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/09b_structured_data_graph.py +0 -0
  61. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/10_skill_lifecycle.py +0 -0
  62. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/11_orchestration_enhancement.py +0 -0
  63. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/12_run_context_serialization.py +0 -0
  64. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/13_human_in_the_loop.py +0 -0
  65. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/14_event_standardization.py +0 -0
  66. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/15_multi_tenant_isolation.py +0 -0
  67. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/16_lifecycle_hooks.py +0 -0
  68. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/17_checkpoint_handoff_resume.py +0 -0
  69. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/18_model_cosplay.py +0 -0
  70. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/README.md +0 -0
  71. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/standard/__init__.py +0 -0
  72. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/examples/test_ollama.py +0 -0
  73. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/__init__.py +0 -0
  74. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/adapters/__init__.py +0 -0
  75. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/adapters/anthropic_adapter.py +0 -0
  76. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/adapters/google_adapter.py +0 -0
  77. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/adapters/ollama_adapter.py +0 -0
  78. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/adapters/openai_adapter.py +0 -0
  79. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/adapters/openai_compatible.py +0 -0
  80. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/base.py +0 -0
  81. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/cache.py +0 -0
  82. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/middleware.py +0 -0
  83. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/registry.py +0 -0
  84. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/llm/types.py +0 -0
  85. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/memory/__init__.py +0 -0
  86. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/memory/base.py +0 -0
  87. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/memory/mem0_provider.py +0 -0
  88. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/ni.agentkit.egg-info/dependency_links.txt +0 -0
  89. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/ni.agentkit.egg-info/entry_points.txt +0 -0
  90. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/ni.agentkit.egg-info/requires.txt +0 -0
  91. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/ni.agentkit.egg-info/top_level.txt +0 -0
  92. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/runner/__init__.py +0 -0
  93. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/runner/context.py +0 -0
  94. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/runner/context_store.py +0 -0
  95. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/runner/runner.py +0 -0
  96. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/safety/__init__.py +0 -0
  97. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/safety/guardrails.py +0 -0
  98. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/safety/permissions.py +0 -0
  99. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/setup.cfg +0 -0
  100. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/skills/__init__.py +0 -0
  101. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/skills/loader.py +0 -0
  102. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/skills/models.py +0 -0
  103. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/skills/registry.py +0 -0
  104. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tests/test_after_callback.py +0 -0
  105. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tests/test_quickstart_core.py +0 -0
  106. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tests/test_runner_checkpoint_resume.py +0 -0
  107. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tools/__init__.py +0 -0
  108. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tools/base_tool.py +0 -0
  109. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tools/function_tool.py +0 -0
  110. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tools/nebula_tool.py +0 -0
  111. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tools/skill_toolset.py +0 -0
  112. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tools/sqlite_tool.py +0 -0
  113. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/tools/structured_data.py +0 -0
  114. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/utils/__init__.py +0 -0
  115. {ni_agentkit-0.6.2 → ni_agentkit-0.6.3}/utils/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ni.agentkit
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: A Python-native Agent framework with first-class Skill support and multi-LLM adapter
5
5
  Author-email: Krix Tam <krix.tam@qq.com>
6
6
  License: MIT
@@ -153,8 +153,8 @@ python "$(python -c "import agentkit, os; print(os.path.join(agentkit.get_exampl
153
153
 
154
154
  ```bash
155
155
  dist/
156
- ├── ni_agentkit-0.6.2-py3-none-any.whl # pip install 用这个
157
- └── ni_agentkit-0.6.2.tar.gz # 源码分发
156
+ ├── ni_agentkit-0.6.3-py3-none-any.whl # pip install 用这个
157
+ └── ni_agentkit-0.6.3.tar.gz # 源码分发
158
158
  ```
159
159
 
160
160
  ## 📄 License
@@ -111,8 +111,8 @@ python "$(python -c "import agentkit, os; print(os.path.join(agentkit.get_exampl
111
111
 
112
112
  ```bash
113
113
  dist/
114
- ├── ni_agentkit-0.6.2-py3-none-any.whl # pip install 用这个
115
- └── ni_agentkit-0.6.2.tar.gz # 源码分发
114
+ ├── ni_agentkit-0.6.3-py3-none-any.whl # pip install 用这个
115
+ └── ni_agentkit-0.6.3.tar.gz # 源码分发
116
116
  ```
117
117
 
118
118
  ## 📄 License
@@ -31,7 +31,7 @@ from .tools.function_tool import FunctionTool, function_tool
31
31
  from .tools.structured_data import ResultFormatter, StructuredDataTool
32
32
  from .tools.sqlite_tool import SQLiteTool, SQLiteResultFormatter
33
33
 
34
- __version__ = "0.6.2"
34
+ __version__ = "0.6.3"
35
35
 
36
36
 
37
37
  def get_docs_dir() -> str:
@@ -1,6 +1,6 @@
1
1
  # AgentKit 快速入门教程
2
2
 
3
- > 本教程将带你从零开始,通过 18 组由简到繁的示例(含 9A/9B),掌握 AgentKit 的核心用法。
3
+ > 本教程将带你从零开始,通过 18 组由简到繁的示例(含 8A/8B/8C、9A/9B),掌握 AgentKit 的核心用法。
4
4
 
5
5
  ---
6
6
 
@@ -608,60 +608,45 @@ asyncio.run(stream_demo())
608
608
 
609
609
  默认情况下,Agent 每次运行都是无状态的。配上记忆后,Agent 可以记住用户的偏好和历史。
610
610
 
611
- ### 无需外部依赖的 SimpleMemory
611
+ ### 8A:SimpleMemory
612
612
 
613
613
  ```python
614
614
  from agentkit import Agent, Runner, BaseMemoryProvider, Memory
615
615
 
616
616
  class SimpleMemory(BaseMemoryProvider):
617
- """轻量内存记忆——适合开发测试"""
618
617
  def __init__(self):
619
618
  self._store, self._counter = [], 0
620
-
621
619
  async def add(self, content, *, user_id=None, agent_id=None, metadata=None):
622
620
  self._counter += 1
623
621
  m = Memory(id=str(self._counter), content=content)
624
622
  self._store.append(m)
625
623
  return [m]
626
-
627
624
  async def search(self, query, *, user_id=None, agent_id=None, limit=10):
628
- # 简单关键词匹配(生产环境用 Mem0 的向量搜索)
629
625
  query_words = set(query)
630
626
  scored = [(len(query_words & set(m.content)), m) for m in self._store]
631
627
  scored.sort(reverse=True, key=lambda x: x[0])
632
628
  return [m for s, m in scored[:limit] if s > 0]
633
-
634
629
  async def get_all(self, *, user_id=None, agent_id=None):
635
630
  return list(self._store)
636
-
637
631
  async def delete(self, memory_id):
638
632
  self._store = [m for m in self._store if m.id != memory_id]
639
633
  return True
640
634
 
641
- # 给 Agent 配上记忆
642
635
  memory = SimpleMemory()
643
-
644
636
  agent = Agent(
645
637
  name="personal-assistant",
646
638
  instructions="你是一个贴心的个人助手。根据记忆来个性化回答。",
647
639
  model="ollama/qwen3.5:cloud",
648
- memory=memory, # ← 加上这一行
640
+ memory=memory,
641
+ memory_async_write=False,
649
642
  )
650
643
 
651
- # 第 1 轮:告诉偏好
652
644
  Runner.run_sync(agent, input="我叫小明,我喜欢喝咖啡,讨厌喝茶", user_id="krix")
653
-
654
- # 第 2 轮:Agent 会记住偏好,推荐咖啡而不是茶
655
645
  result = Runner.run_sync(agent, input="帮我推荐一杯饮料", user_id="krix")
656
- print(result.final_output) # "推荐拿铁!" — 基于记忆推荐
646
+ print(result.final_output) # 基于记忆推荐
657
647
  ```
658
648
 
659
- **要点**:
660
- - 默认不开启记忆,需要给 `Agent` 传 `memory=...` 参数
661
- - 配好后**不需要写额外代码**——框架自动在对话前检索记忆、对话后存储记忆
662
- - `user_id` 用于多用户隔离
663
-
664
- ### 生产环境使用 Mem0
649
+ ### 8B:Mem0Provider(生产级)
665
650
 
666
651
  ```bash
667
652
  pip install mem0ai
@@ -678,11 +663,47 @@ memory = Mem0Provider({
678
663
  }
679
664
  })
680
665
 
681
- agent = Agent(memory=memory, ...) # 替换 SimpleMemory 即可
666
+ agent = Agent(memory=memory, ...) # 先构建 memory,再注入 Agent
682
667
  ```
683
668
 
684
669
  Mem0 相比 SimpleMemory 的优势:**语义搜索**(理解意思而非关键词)、**持久化**(重启不丢失)、**智能提取**(从对话中自动抽取关键信息)。
685
670
 
671
+ ### 8C:自定义 Memory(文件持久化)
672
+
673
+ ```python
674
+ import json
675
+ from pathlib import Path
676
+ from agentkit import BaseMemoryProvider, Memory, Agent
677
+
678
+ class FileMemoryProvider(BaseMemoryProvider):
679
+ def __init__(self, file_path: str):
680
+ self.path = Path(file_path)
681
+ self.records = json.loads(self.path.read_text()) if self.path.exists() else []
682
+ async def add(self, content, *, user_id=None, agent_id=None, metadata=None):
683
+ new_id = str(len(self.records) + 1)
684
+ self.records.append({"id": new_id, "content": content, "user_id": user_id})
685
+ self.path.write_text(json.dumps(self.records, ensure_ascii=False, indent=2))
686
+ return [Memory(id=new_id, content=content)]
687
+ async def search(self, query, *, user_id=None, agent_id=None, limit=10):
688
+ q = query.lower()
689
+ out = [r for r in self.records if q in r["content"].lower()]
690
+ return [Memory(id=r["id"], content=r["content"]) for r in out[:limit]]
691
+ async def get_all(self, *, user_id=None, agent_id=None):
692
+ return [Memory(id=r["id"], content=r["content"]) for r in self.records]
693
+ async def delete(self, memory_id):
694
+ self.records = [r for r in self.records if r["id"] != memory_id]
695
+ self.path.write_text(json.dumps(self.records, ensure_ascii=False, indent=2))
696
+ return True
697
+
698
+ memory = FileMemoryProvider("/tmp/agentkit_memory.json")
699
+ agent = Agent(memory=memory, ...)
700
+ ```
701
+
702
+ **要点**:
703
+ - 默认不开启记忆,需要给 `Agent` 传 `memory=...` 参数
704
+ - `SimpleMemory` 适合开发测试,`Mem0Provider` 适合生产语义检索
705
+ - 自定义 `FileMemoryProvider` 可实现轻量持久化(进程重启后仍可读取)
706
+
686
707
  ---
687
708
 
688
709
  ## 示例 9A:关系型数据库 — 防止 SQL 注入的参数化 Tool
@@ -1159,7 +1180,9 @@ agent = Agent(name="assistant", instructions="...")
1159
1180
  | [`05_guardrail.py`](../examples/standard/05_guardrail.py) | 示例 5:安全护栏 |
1160
1181
  | [`06_orchestration.py`](../examples/standard/06_orchestration.py) | 示例 6:编排 Agent |
1161
1182
  | [`07_sync_async_stream.py`](../examples/standard/07_sync_async_stream.py) | 示例 7:同步/异步/流式运行 |
1162
- | [`08_memory.py`](../examples/standard/08_memory.py) | 示例 8:记忆系统 |
1183
+ | [`08a_memory_simple_provider.py`](../examples/standard/08a_memory_simple_provider.py) | 示例 8A:记忆系统(SimpleMemory) |
1184
+ | [`08b_memory_mem0_provider.py`](../examples/standard/08b_memory_mem0_provider.py) | 示例 8B:记忆系统(Mem0Provider) |
1185
+ | [`08c_memory_file_provider.py`](../examples/standard/08c_memory_file_provider.py) | 示例 8C:记忆系统(文件持久化) |
1163
1186
  | [`09a_structured_data_sql.py`](../examples/standard/09a_structured_data_sql.py) | 示例 9A:关系型数据库 |
1164
1187
  | [`09b_structured_data_graph.py`](../examples/standard/09b_structured_data_graph.py) | 示例 9B:图数据库 |
1165
1188
  | [`10_skill_lifecycle.py`](../examples/standard/10_skill_lifecycle.py) | 示例 10:Skill 生命周期 |
@@ -1183,7 +1206,9 @@ agent = Agent(name="assistant", instructions="...")
1183
1206
  | [`05_guardrail.py`](../examples/ollama/05_guardrail.py) | 示例 5:安全护栏 |
1184
1207
  | [`06_orchestration.py`](../examples/ollama/06_orchestration.py) | 示例 6:编排 Agent |
1185
1208
  | [`07_sync_async_stream.py`](../examples/ollama/07_sync_async_stream.py) | 示例 7:同步/异步/流式运行 |
1186
- | [`08_memory.py`](../examples/ollama/08_memory.py) | 示例 8:记忆系统 |
1209
+ | [`08a_memory_simple_provider.py`](../examples/ollama/08a_memory_simple_provider.py) | 示例 8A:记忆系统(SimpleMemory) |
1210
+ | [`08b_memory_mem0_provider.py`](../examples/ollama/08b_memory_mem0_provider.py) | 示例 8B:记忆系统(Mem0Provider) |
1211
+ | [`08c_memory_file_provider.py`](../examples/ollama/08c_memory_file_provider.py) | 示例 8C:记忆系统(文件持久化) |
1187
1212
  | [`09a_structured_data_sql.py`](../examples/ollama/09a_structured_data_sql.py) | 示例 9A:关系型数据库 |
1188
1213
  | [`09b_structured_data_graph.py`](../examples/ollama/09b_structured_data_graph.py) | 示例 9B:图数据库 |
1189
1214
  | [`10_skill_lifecycle.py`](../examples/ollama/10_skill_lifecycle.py) | 示例 10:Skill 生命周期 |
@@ -3,7 +3,7 @@
3
3
  > Python 原生的 Agent 开发框架,内置一等公民级别的 Skill 支持和自研多模型适配层。
4
4
 
5
5
  [![Python](https://img.shields.io/badge/Python-≥3.11-blue.svg)](https://python.org)
6
- [![Version](https://img.shields.io/badge/Version-0.6.2-green.svg)]()
6
+ [![Version](https://img.shields.io/badge/Version-0.6.3-green.svg)]()
7
7
  [![License](https://img.shields.io/badge/License-MIT-yellow.svg)]()
8
8
 
9
9
  ---
@@ -0,0 +1,90 @@
1
+ # AgentKit 示例测试报告
2
+
3
+ > 测试时间:`2026-04-23`
4
+ > 测试环境:`macOS (Apple Silicon)`
5
+ > 模型:Ollama `qwen3.5:cloud`
6
+ > AgentKit 版本:v0.6.3
7
+ > Thinking 模式:开启(默认)
8
+ > LLM 调用模式:非流式(默认)
9
+ > 缓存:开启(默认)
10
+ > 执行脚本:`examples/test_ollama.py`
11
+
12
+ ---
13
+
14
+ ## 测试结果
15
+
16
+ | # | 示例 | 文件 | 耗时 | 状态 | 说明 |
17
+ |---|------|------|-----:|:----:|------|
18
+ | 1 | 基础对话 | `01_basic_chat.py` | 16.51s | ✅ | 运行通过 |
19
+ | 2 | 工具调用 | `02_tool_calling.py` | 12.51s | ✅ | 运行通过 |
20
+ | 3 | Skill 使用 | `03_skill_usage.py` | 82.73s | ✅ | 运行通过 |
21
+ | 4 | 多 Agent 协作 | `04_multi_agent.py` | 97.29s | ✅ | 运行通过 |
22
+ | 5 | 安全护栏 | `05_guardrail.py` | 7.33s | ✅ | 运行通过 |
23
+ | 5H | HITL 工具触发(Playground 专用) | `05_human_in_the_loop.py` | 0.18s | ✅ | 运行通过 |
24
+ | 6 | 编排 Agent | `06_orchestration.py` | 290.84s | ✅ | 运行通过 |
25
+ | 7 | 同步/异步/流式 | `07_sync_async_stream.py` | 13.51s | ✅ | 运行通过 |
26
+ | 8 | 记忆系统(综合) | `08_memory.py` | 537.55s | ✅ | 运行通过(本轮最慢) |
27
+ | 8A | SimpleMemory | `08a_memory_simple_provider.py` | 45.82s | ✅ | 运行通过 |
28
+ | 8B | Mem0Provider | `08b_memory_mem0_provider.py` | 0.19s | ✅ | 未配置 `OPENAI_API_KEY` 时自动跳过 |
29
+ | 8C | 文件持久化 Memory | `08c_memory_file_provider.py` | 157.72s | ✅ | 运行通过 |
30
+ | 9A | 结构化数据(SQL) | `09a_structured_data_sql.py` | 5.72s | ✅ | 运行通过 |
31
+ | 9B | 结构化数据(图) | `09b_structured_data_graph.py` | 30.72s | ✅ | 运行通过 |
32
+ | 10 | Skill 生命周期 | `10_skill_lifecycle.py` | 2.36s | ✅ | 运行通过 |
33
+ | 11 | 编排增强 | `11_orchestration_enhancement.py` | 57.63s | ✅ | 运行通过 |
34
+ | 12 | 序列化协议 | `12_run_context_serialization.py` | 0.19s | ✅ | 运行通过 |
35
+ | 13 | Human in the Loop | `13_human_in_the_loop.py` | 8.06s | ✅ | 运行通过 |
36
+ | 14 | Event 标准化 | `14_event_standardization.py` | 5.70s | ✅ | 运行通过 |
37
+ | 15 | 多租户隔离 | `15_multi_tenant_isolation.py` | 0.29s | ✅ | 运行通过 |
38
+ | 16 | 生命周期 Hooks | `16_lifecycle_hooks.py` | 0.27s | ✅ | 运行通过 |
39
+ | 17 | Checkpoint + Handoff + Resume | `17_checkpoint_handoff_resume.py` | 0.18s | ✅ | 运行通过 |
40
+ | 18 | ModelCosplay | `18_model_cosplay.py` | 0.18s | ✅ | 运行通过 |
41
+ | 19 | HITL 确定性触发 | `19_hitl_deterministic.py` | 0.17s | ✅ | 运行通过(本轮最快) |
42
+ | | **合计** | | **1373.65s** | **24/24** | |
43
+
44
+ ## 耗时分析
45
+
46
+ - **最快示例**:19 HITL 确定性触发(0.17s)
47
+ - **最慢示例**:8 记忆系统(537.55s)
48
+ - **耗时集中区间**:涉及多轮推理/记忆写入/编排循环的示例耗时显著更高
49
+
50
+ ## 各示例 LLM 调用次数估算
51
+
52
+ | # | 示例 | LLM 调用次数 | 说明 |
53
+ |---|------|:-----------:|------|
54
+ | 1 | 基础对话 | 1 | 单次对话 |
55
+ | 2 | 工具调用 | ~6 | 多次工具调用与回复生成 |
56
+ | 3 | Skill 使用 | ~9 | load_skill + 工具调用 + 回复 |
57
+ | 4 | 多 Agent 协作 | ~8 | as_tool + handoff 链路 |
58
+ | 5 | 安全护栏 | ~3 | 拦截与放行混合路径 |
59
+ | 5H | HITL 工具触发(Playground 专用) | ~0 | 仅触发挂起事件,不走 LLM |
60
+ | 6 | 编排 Agent | ~12 | Sequential + Parallel + Loop |
61
+ | 7 | 同步/异步/流式 | ~7 | 三种运行模式覆盖 |
62
+ | 8 | 记忆系统(综合) | ~10 | 记忆读写与多轮对话 |
63
+ | 8A | SimpleMemory | ~2 | 轻量关键词检索 |
64
+ | 8B | Mem0Provider | ~0 | 未配置 `OPENAI_API_KEY` 时自动跳过 |
65
+ | 8C | 文件持久化 Memory | ~2 | 本地文件读写 + 记忆检索 |
66
+ | 9A | 结构化数据(SQL) | ~2 | 参数化查询 + 汇总 |
67
+ | 9B | 结构化数据(图) | ~2 | 图查询 + 汇总 |
68
+ | 10 | Skill 生命周期 | 1 | 单轮校验 |
69
+ | 11 | 编排增强 | ~4 | loop_condition + early_exit |
70
+ | 12 | RunContext 序列化 | 0 | 纯本地序列化 |
71
+ | 13 | HITL 断点续跑 | ~3 | 挂起 + 恢复 |
72
+ | 14 | 事件协议标准化 | 1 | 标准事件输出 |
73
+ | 15 | 多租户隔离 | ~3 | 多会话隔离验证 |
74
+ | 16 | 生命周期 Hooks | ~2 | Hook 链路验证 |
75
+ | 17 | Checkpoint Handoff 恢复 | 0 | 自定义事件流,不依赖 LLM |
76
+ | 18 | ModelCosplay | 0 | 仅验证模型改写开关与运行时覆盖逻辑,不调用 LLM |
77
+ | 19 | HITL 确定性触发 | 0 | 自定义事件流,不依赖 LLM |
78
+
79
+ ## 已知问题
80
+
81
+ | 问题 | 严重程度 | 说明 |
82
+ |------|:--------:|------|
83
+ | 运行异常 | - | 无,24 个示例全部通过(8B 在无 `OPENAI_API_KEY` 时自动跳过) |
84
+
85
+ ## 运行方式
86
+
87
+ ```bash
88
+ # 在 agentkit 目录执行
89
+ python examples/test_ollama.py
90
+ ```
@@ -21,14 +21,15 @@ agent = Agent(
21
21
  )
22
22
 
23
23
  # 同步运行
24
- result = Runner.run_sync(agent, input="什么是量子计算?请用一句话解释。")
24
+ if __name__ == "__main__":
25
+ result = Runner.run_sync(agent, input="什么是量子计算?请用一句话解释。")
25
26
 
26
- if result.success:
27
- print(f"✅ 回复: {result.final_output}")
28
- else:
29
- print(f"❌ 错误: {result.error}")
27
+ if result.success:
28
+ print(f"✅ 回复: {result.final_output}")
29
+ else:
30
+ print(f"❌ 错误: {result.error}")
30
31
 
31
- # 查看事件流
32
- print("\n📋 事件流:")
33
- for event in result.events:
34
- print(f" [{event.type}] {str(event.data)[:80]}")
32
+ # 查看事件流
33
+ print("\n📋 事件流:")
34
+ for event in result.events:
35
+ print(f" [{event.type}] {str(event.data)[:80]}")
@@ -0,0 +1,42 @@
1
+ """
2
+ 示例 5(Playground/HITL 专用):Human-in-the-loop Agent(Ollama 版)
3
+
4
+ 用途:
5
+ - 提供一个可被 AgentHub `entry` 直接加载的 `agent` 实例;
6
+ - 在工具执行前通过 `request_human_input(...)` 触发挂起,便于在 Playground 中演示 HITL。
7
+ """
8
+ import os
9
+ import sys
10
+
11
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
12
+
13
+ from agentkit import Agent
14
+ from agentkit.tools.base_tool import request_human_input
15
+ from agentkit.tools.function_tool import FunctionTool
16
+
17
+
18
+ def confirm_action(action: str) -> str:
19
+ """敏感操作执行前请求人工确认。"""
20
+ request_human_input(f"即将执行敏感操作: {action},请确认 (approve/reject)")
21
+
22
+
23
+ def execute_action(action: str) -> str:
24
+ """执行操作(示例返回)。"""
25
+ return f"操作 '{action}' 已执行。"
26
+
27
+
28
+ confirm_tool = FunctionTool.from_function(confirm_action)
29
+ execute_tool = FunctionTool.from_function(execute_action)
30
+
31
+
32
+ agent = Agent(
33
+ name="hitl-agent",
34
+ instructions=(
35
+ "你是一个需要人工审批的运维助手。"
36
+ "当用户要求执行敏感操作时,必须先调用 confirm_action 请求人工确认;"
37
+ "收到确认后再调用 execute_action。"
38
+ ),
39
+ # 使用纯本地模型,避免 cloud 变体在未鉴权/网络抖动时触发远端 502。
40
+ model="ollama/qwen3.5:4b",
41
+ tools=[confirm_tool, execute_tool],
42
+ )
@@ -0,0 +1,59 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+
5
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
6
+
7
+ from agentkit import Agent, BaseMemoryProvider, Memory, Runner
8
+
9
+ MODEL = "ollama/qwen3.5:cloud"
10
+
11
+
12
+ class SimpleMemory(BaseMemoryProvider):
13
+ def __init__(self) -> None:
14
+ self._store: list[Memory] = []
15
+ self._counter = 0
16
+
17
+ async def add(self, content, *, user_id=None, agent_id=None, metadata=None):
18
+ self._counter += 1
19
+ item = Memory(id=str(self._counter), content=content)
20
+ self._store.append(item)
21
+ return [item]
22
+
23
+ async def search(self, query, *, user_id=None, agent_id=None, limit=10):
24
+ query_chars = set(query)
25
+ scored: list[tuple[int, Memory]] = []
26
+ for item in self._store:
27
+ overlap = len(query_chars & set(item.content))
28
+ if overlap > 0:
29
+ scored.append((overlap, item))
30
+ scored.sort(reverse=True, key=lambda x: x[0])
31
+ return [m for _, m in scored[:limit]]
32
+
33
+ async def get_all(self, *, user_id=None, agent_id=None):
34
+ return list(self._store)
35
+
36
+ async def delete(self, memory_id):
37
+ self._store = [m for m in self._store if m.id != memory_id]
38
+ return True
39
+
40
+
41
+ async def main() -> None:
42
+ print("=== 示例 8A:SimpleMemory ===")
43
+ memory = SimpleMemory()
44
+ agent = Agent(
45
+ name="remembering",
46
+ instructions="你是贴心助手。根据记忆回答,回答简洁。",
47
+ model=MODEL,
48
+ memory=memory,
49
+ memory_async_write=False,
50
+ )
51
+
52
+ await Runner.run(agent, input="我叫小明,喜欢咖啡,讨厌茶。", user_id="user_001")
53
+ result = await Runner.run(agent, input="帮我推荐一杯饮料。", user_id="user_001")
54
+ print("推荐:", result.final_output)
55
+ print("记忆条数:", len(await memory.get_all()))
56
+
57
+
58
+ if __name__ == "__main__":
59
+ asyncio.run(main())
@@ -0,0 +1,57 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+
5
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
6
+
7
+ from agentkit import Agent, Runner
8
+
9
+ MODEL = "ollama/qwen3.5:cloud"
10
+
11
+
12
+ async def main() -> None:
13
+ print("=== 示例 8B:Mem0Provider ===")
14
+ try:
15
+ from agentkit.memory.mem0_provider import Mem0Provider
16
+ except Exception:
17
+ print("未安装 mem0ai,跳过运行。")
18
+ print("安装: pip install mem0ai")
19
+ print("并启动 qdrant: docker run -p 6333:6333 qdrant/qdrant")
20
+ return
21
+
22
+ if not os.getenv("OPENAI_API_KEY"):
23
+ print("未设置 OPENAI_API_KEY,跳过运行。")
24
+ return
25
+
26
+ try:
27
+ memory = Mem0Provider(
28
+ {
29
+ "vector_store": {
30
+ "provider": "qdrant",
31
+ "config": {
32
+ "collection_name": "agentkit_quickstart",
33
+ "host": "localhost",
34
+ "port": 6333,
35
+ },
36
+ }
37
+ }
38
+ )
39
+ except Exception as exc:
40
+ print(f"Mem0 初始化失败,跳过运行: {exc}")
41
+ return
42
+
43
+ agent = Agent(
44
+ name="mem0-assistant",
45
+ instructions="你是贴心助手。根据记忆回答,回答简洁。",
46
+ model=MODEL,
47
+ memory=memory,
48
+ memory_async_write=False,
49
+ )
50
+
51
+ await Runner.run(agent, input="记住:我喜欢低糖拿铁。", user_id="user_001")
52
+ result = await Runner.run(agent, input="给我推荐一杯饮料。", user_id="user_001")
53
+ print("推荐:", result.final_output)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ asyncio.run(main())
@@ -0,0 +1,102 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
8
+
9
+ from agentkit import Agent, BaseMemoryProvider, Memory, Runner
10
+
11
+ MODEL = "ollama/qwen3.5:cloud"
12
+ MEMORY_FILE = "/tmp/agentkit_memory_ollama.json"
13
+
14
+
15
+ class FileMemoryProvider(BaseMemoryProvider):
16
+ def __init__(self, file_path: str) -> None:
17
+ self._path = Path(file_path)
18
+ self._records: list[dict] = []
19
+ self._counter = 0
20
+ self._load()
21
+
22
+ def _load(self) -> None:
23
+ if self._path.exists():
24
+ try:
25
+ self._records = json.loads(self._path.read_text(encoding="utf-8"))
26
+ except Exception:
27
+ self._records = []
28
+ if self._records:
29
+ self._counter = max(int(r.get("id", 0)) for r in self._records)
30
+
31
+ def _save(self) -> None:
32
+ self._path.parent.mkdir(parents=True, exist_ok=True)
33
+ self._path.write_text(json.dumps(self._records, ensure_ascii=False, indent=2), encoding="utf-8")
34
+
35
+ @staticmethod
36
+ def _to_memory(record: dict) -> Memory:
37
+ return Memory(id=str(record["id"]), content=str(record["content"]))
38
+
39
+ async def add(self, content, *, user_id=None, agent_id=None, metadata=None):
40
+ self._counter += 1
41
+ record = {
42
+ "id": str(self._counter),
43
+ "content": content,
44
+ "user_id": user_id,
45
+ "agent_id": agent_id,
46
+ "metadata": metadata or {},
47
+ }
48
+ self._records.append(record)
49
+ self._save()
50
+ return [self._to_memory(record)]
51
+
52
+ async def search(self, query, *, user_id=None, agent_id=None, limit=10):
53
+ q = str(query).lower()
54
+ matched: list[Memory] = []
55
+ for record in self._records:
56
+ if user_id is not None and record.get("user_id") != user_id:
57
+ continue
58
+ if agent_id is not None and record.get("agent_id") != agent_id:
59
+ continue
60
+ if q in str(record.get("content", "")).lower():
61
+ matched.append(self._to_memory(record))
62
+ if len(matched) >= limit:
63
+ break
64
+ return matched
65
+
66
+ async def get_all(self, *, user_id=None, agent_id=None):
67
+ out: list[Memory] = []
68
+ for record in self._records:
69
+ if user_id is not None and record.get("user_id") != user_id:
70
+ continue
71
+ if agent_id is not None and record.get("agent_id") != agent_id:
72
+ continue
73
+ out.append(self._to_memory(record))
74
+ return out
75
+
76
+ async def delete(self, memory_id):
77
+ before = len(self._records)
78
+ self._records = [r for r in self._records if str(r.get("id")) != str(memory_id)]
79
+ self._save()
80
+ return len(self._records) < before
81
+
82
+
83
+ async def main() -> None:
84
+ print("=== 示例 8C:自定义 FileMemoryProvider(文件持久化) ===")
85
+ memory = FileMemoryProvider(MEMORY_FILE)
86
+ agent = Agent(
87
+ name="file-memory-assistant",
88
+ instructions="你是贴心助手。根据记忆回答,回答简洁。",
89
+ model=MODEL,
90
+ memory=memory,
91
+ memory_async_write=False,
92
+ )
93
+
94
+ await Runner.run(agent, input="记住:我早餐喜欢美式咖啡。", user_id="user_001")
95
+ result = await Runner.run(agent, input="推荐一杯饮料。", user_id="user_001")
96
+ print("推荐:", result.final_output)
97
+ print("持久化文件:", MEMORY_FILE)
98
+ print("当前记忆数:", len(await memory.get_all(user_id="user_001")))
99
+
100
+
101
+ if __name__ == "__main__":
102
+ asyncio.run(main())
@@ -0,0 +1,57 @@
1
+ """
2
+ 示例 19(Playground/HITL 专用):确定性触发挂起(必现)
3
+
4
+ 用途:
5
+ - 提供一个可被 AgentHub `entry` 直接加载的 `agent` 实例;
6
+ - 首次运行必然发出 `suspend_requested`,便于 Playground 稳定演示 HITL 闭环。
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import sys
12
+ from typing import AsyncGenerator
13
+
14
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", ".."))
15
+
16
+ from agentkit.agents.base_agent import BaseAgent
17
+ from agentkit.runner.context import RunContext
18
+ from agentkit.runner.events import Event, EventType
19
+
20
+
21
+ class DeterministicHITLAgent(BaseAgent):
22
+ async def _run_impl(self, ctx: RunContext) -> AsyncGenerator[Event, None]:
23
+ # 第一次进入会话时,确定性触发挂起。
24
+ if not ctx.state.get("deterministic_hitl_suspended_once"):
25
+ ctx.state["deterministic_hitl_suspended_once"] = True
26
+ suspension = ctx.register_suspension(
27
+ tool_call_id="det-hitl-1",
28
+ tool_name="manual_approval",
29
+ prompt="请审批该操作(approve/reject)",
30
+ )
31
+ yield Event(
32
+ agent=self.name,
33
+ type=EventType.SUSPEND_REQUESTED,
34
+ data={
35
+ "suspension_id": suspension.suspension_id,
36
+ "prompt": "请审批该操作(approve/reject)",
37
+ "tool": "manual_approval",
38
+ "tool_call_id": "det-hitl-1",
39
+ },
40
+ )
41
+ return
42
+
43
+ decision = "unknown"
44
+ for msg in reversed(ctx.messages):
45
+ if msg.get("role") == "tool" and msg.get("tool_call_id") == "det-hitl-1":
46
+ decision = msg.get("content", "unknown")
47
+ break
48
+
49
+ yield Event(
50
+ agent=self.name,
51
+ type=EventType.FINAL_OUTPUT,
52
+ data=f"已收到人工决策: {decision}",
53
+ )
54
+
55
+
56
+ agent = DeterministicHITLAgent(name="deterministic-hitl-agent")
57
+