ni.agentkit 0.7.3__tar.gz → 0.7.4__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 (141) hide show
  1. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/PKG-INFO +3 -3
  2. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/README.md +2 -2
  3. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/agents/agent.py +6 -2
  4. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/docs/Architecture.md +1 -0
  5. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/docs/README.md +1 -1
  6. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/docs/Reference.md +14 -0
  7. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/docs/TestReport.md +31 -31
  8. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/cache.py +19 -3
  9. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/registry.py +16 -0
  10. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/ni.agentkit.egg-info/PKG-INFO +3 -3
  11. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/ni.agentkit.egg-info/SOURCES.txt +3 -0
  12. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/pyproject.toml +1 -1
  13. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/rag/file_memory_provider.py +32 -6
  14. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/rag/sqlite_store.py +11 -2
  15. ni_agentkit-0.7.4/tests/test_default_model_env.py +15 -0
  16. ni_agentkit-0.7.4/utils/env.py +72 -0
  17. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/LICENSE +0 -0
  18. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/__init__.py +0 -0
  19. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/_cli.py +0 -0
  20. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/agents/__init__.py +0 -0
  21. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/agents/base_agent.py +0 -0
  22. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/agents/orchestrators.py +0 -0
  23. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/docs/QuickStart.md +0 -0
  24. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/Skill.md +0 -0
  25. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/__init__.py +0 -0
  26. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/01_basic_chat.py +0 -0
  27. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/02_tool_calling.py +0 -0
  28. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/03_skill_usage.py +0 -0
  29. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/03b_skill_tools_entry.py +0 -0
  30. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/04_multi_agent.py +0 -0
  31. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/05_guardrail.py +0 -0
  32. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/05_human_in_the_loop.py +0 -0
  33. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/06_orchestration.py +0 -0
  34. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/07_sync_async_stream.py +0 -0
  35. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/08_memory.py +0 -0
  36. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/08a_memory_simple_provider.py +0 -0
  37. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/08b_memory_mem0_provider.py +0 -0
  38. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/08c_memory_file_provider.py +0 -0
  39. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/09a_structured_data_sql.py +0 -0
  40. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/09b_structured_data_graph.py +0 -0
  41. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/09c_nebula_graph_tool.py +0 -0
  42. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/10_skill_lifecycle.py +0 -0
  43. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/11_orchestration_enhancement.py +0 -0
  44. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/12_run_context_serialization.py +0 -0
  45. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/13_human_in_the_loop.py +0 -0
  46. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/14_event_standardization.py +0 -0
  47. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/15_multi_tenant_isolation.py +0 -0
  48. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/16_lifecycle_hooks.py +0 -0
  49. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/17_checkpoint_handoff_resume.py +0 -0
  50. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/18_model_cosplay.py +0 -0
  51. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/19_hitl_deterministic.py +0 -0
  52. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/20_simple_rag_agent.py +0 -0
  53. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/README.md +0 -0
  54. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/__init__.py +0 -0
  55. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/ollama/model_config.py +0 -0
  56. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/quickstart.py +0 -0
  57. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/skills/weather-tools-entry/SKILL.md +0 -0
  58. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/skills/weather-tools-entry/tools/weather_tools.py +0 -0
  59. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/01_basic_chat.py +0 -0
  60. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/02_tool_calling.py +0 -0
  61. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/03_skill_usage.py +0 -0
  62. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/03b_skill_tools_entry.py +0 -0
  63. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/04_multi_agent.py +0 -0
  64. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/05_guardrail.py +0 -0
  65. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/05_human_in_the_loop.py +0 -0
  66. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/06_orchestration.py +0 -0
  67. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/07_sync_async_stream.py +0 -0
  68. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/08_memory.py +0 -0
  69. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/08a_memory_simple_provider.py +0 -0
  70. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/08b_memory_mem0_provider.py +0 -0
  71. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/08c_memory_file_provider.py +0 -0
  72. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/09a_structured_data_sql.py +0 -0
  73. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/09b_structured_data_graph.py +0 -0
  74. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/09c_nebula_graph_tool.py +0 -0
  75. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/10_skill_lifecycle.py +0 -0
  76. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/11_orchestration_enhancement.py +0 -0
  77. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/12_run_context_serialization.py +0 -0
  78. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/13_human_in_the_loop.py +0 -0
  79. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/14_event_standardization.py +0 -0
  80. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/15_multi_tenant_isolation.py +0 -0
  81. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/16_lifecycle_hooks.py +0 -0
  82. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/17_checkpoint_handoff_resume.py +0 -0
  83. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/18_model_cosplay.py +0 -0
  84. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/19_hitl_deterministic.py +0 -0
  85. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/20_simple_rag_agent.py +0 -0
  86. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/README.md +0 -0
  87. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/__init__.py +0 -0
  88. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/standard/model_config.py +0 -0
  89. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/test_ollama.py +0 -0
  90. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/examples/test_standard.py +0 -0
  91. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/__init__.py +0 -0
  92. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/adapters/__init__.py +0 -0
  93. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/adapters/anthropic_adapter.py +0 -0
  94. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/adapters/google_adapter.py +0 -0
  95. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/adapters/ollama_adapter.py +0 -0
  96. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/adapters/openai_adapter.py +0 -0
  97. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/adapters/openai_compatible.py +0 -0
  98. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/base.py +0 -0
  99. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/middleware.py +0 -0
  100. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/llm/types.py +0 -0
  101. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/memory/__init__.py +0 -0
  102. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/memory/base.py +0 -0
  103. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/memory/mem0_provider.py +0 -0
  104. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/ni.agentkit.egg-info/dependency_links.txt +0 -0
  105. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/ni.agentkit.egg-info/entry_points.txt +0 -0
  106. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/ni.agentkit.egg-info/requires.txt +0 -0
  107. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/ni.agentkit.egg-info/top_level.txt +0 -0
  108. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/rag/__init__.py +0 -0
  109. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/rag/document_store.py +0 -0
  110. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/rag/loaders.py +0 -0
  111. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/rag/retrievers.py +0 -0
  112. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/rag/simple_rag_agent.py +0 -0
  113. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/rag/types.py +0 -0
  114. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/runner/__init__.py +0 -0
  115. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/runner/context.py +0 -0
  116. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/runner/context_store.py +0 -0
  117. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/runner/events.py +0 -0
  118. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/runner/runner.py +0 -0
  119. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/safety/__init__.py +0 -0
  120. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/safety/guardrails.py +0 -0
  121. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/safety/permissions.py +0 -0
  122. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/setup.cfg +0 -0
  123. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/skills/__init__.py +0 -0
  124. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/skills/loader.py +0 -0
  125. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/skills/models.py +0 -0
  126. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/skills/registry.py +0 -0
  127. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tests/test_after_callback.py +0 -0
  128. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tests/test_graph_repository.py +0 -0
  129. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tests/test_quickstart_core.py +0 -0
  130. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tests/test_runner_checkpoint_resume.py +0 -0
  131. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tests/test_simple_rag.py +0 -0
  132. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tests/test_skill_frontmatter.py +0 -0
  133. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tools/__init__.py +0 -0
  134. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tools/base_tool.py +0 -0
  135. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tools/function_tool.py +0 -0
  136. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tools/nebula_tool.py +0 -0
  137. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tools/skill_toolset.py +0 -0
  138. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tools/sqlite_tool.py +0 -0
  139. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/tools/structured_data.py +0 -0
  140. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/utils/__init__.py +0 -0
  141. {ni_agentkit-0.7.3 → ni_agentkit-0.7.4}/utils/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ni.agentkit
3
- Version: 0.7.3
3
+ Version: 0.7.4
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
@@ -155,8 +155,8 @@ python "$(python -c "import agentkit, os; print(os.path.join(agentkit.get_exampl
155
155
 
156
156
  ```bash
157
157
  dist/
158
- ├── ni_agentkit-0.7.3-py3-none-any.whl # pip install 用这个
159
- └── ni_agentkit-0.7.3.tar.gz # 源码分发
158
+ ├── ni_agentkit-0.7.4-py3-none-any.whl # pip install 用这个
159
+ └── ni_agentkit-0.7.4.tar.gz # 源码分发
160
160
  ```
161
161
 
162
162
  ## 📄 License
@@ -112,8 +112,8 @@ python "$(python -c "import agentkit, os; print(os.path.join(agentkit.get_exampl
112
112
 
113
113
  ```bash
114
114
  dist/
115
- ├── ni_agentkit-0.7.3-py3-none-any.whl # pip install 用这个
116
- └── ni_agentkit-0.7.3.tar.gz # 源码分发
115
+ ├── ni_agentkit-0.7.4-py3-none-any.whl # pip install 用这个
116
+ └── ni_agentkit-0.7.4.tar.gz # 源码分发
117
117
  ```
118
118
 
119
119
  ## 📄 License
@@ -293,11 +293,12 @@ class Agent(BaseAgent):
293
293
 
294
294
  # 5. 调用 LLM(支持缓存)
295
295
  cached = False
296
+ cache_key: str | None = None
296
297
 
297
298
  # 检查缓存
298
299
  if self.enable_cache:
299
300
  cache = self._get_cache()
300
- cached_response = cache.get(messages, tool_defs if tool_defs else None)
301
+ cache_key, cached_response = cache.get_with_key(messages, tool_defs if tool_defs else None)
301
302
  if cached_response is not None:
302
303
  response = cached_response
303
304
  cached = True
@@ -318,7 +319,10 @@ class Agent(BaseAgent):
318
319
 
319
320
  # 写入缓存
320
321
  if self.enable_cache:
321
- cache.put(messages, tool_defs if tool_defs else None, response)
322
+ if cache_key is not None:
323
+ cache.put_with_key(cache_key, response)
324
+ else:
325
+ cache.put(messages, tool_defs if tool_defs else None, response)
322
326
 
323
327
  cache_key_ms = 0.0
324
328
  if self.enable_cache:
@@ -471,6 +471,7 @@ agent.clear_cache()
471
471
  > - 缓存仅在单 Agent 实例内有效,重新创建 Agent 会清空缓存
472
472
  > - 默认最大 128 条缓存,LRU 淘汰最久未使用的条目
473
473
  > - 缓存实现内置 key 生成统计:`key_gen_calls / key_gen_total_ms / key_gen_last_ms / key_gen_avg_ms`
474
+ > - 缓存实现对同一轮的缓存 key 计算做了复用(避免 miss 场景下重复算 key)
474
475
 
475
476
  ### 3. 记忆异步写入
476
477
 
@@ -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.7.3-green.svg)]()
6
+ [![Version](https://img.shields.io/badge/Version-0.7.4-green.svg)]()
7
7
  [![License](https://img.shields.io/badge/License-MIT-yellow.svg)]()
8
8
 
9
9
  ---
@@ -577,6 +577,20 @@ from agentkit import LLMRegistry
577
577
  | `LLMRegistry.create_default()` | 创建默认模型实例 |
578
578
  | `LLMRegistry.register(prefix, adapter_class)` | 注册自定义适配器 |
579
579
 
580
+ 默认模型解析优先级:
581
+
582
+ 1. 若代码中调用过 `LLMRegistry.set_default(...)`,优先使用该默认配置。
583
+ 2. 否则会尝试从环境变量(或本地 `.env/.evn`)读取:`AGENTKIT_MODEL` → `AGENTKIT_DEFAULT_MODEL` → `AGENTKIT_STANDARD_MODEL` → `AGENTKIT_OLLAMA_MODEL`。
584
+ 3. 都未配置时回退到内置默认值。
585
+
586
+ 示例:
587
+
588
+ ```bash
589
+ export AGENTKIT_DEFAULT_MODEL="ollama/qwen3.5:cloud"
590
+ # 或在项目根目录写入 .env(无需额外依赖)
591
+ # AGENTKIT_DEFAULT_MODEL=ollama/qwen3.5:cloud
592
+ ```
593
+
580
594
  **前缀路由表**:
581
595
 
582
596
  | 前缀 | 适配器 | 需要的包 |
@@ -3,7 +3,7 @@
3
3
  > 测试时间:`2026-06-15`
4
4
  > 测试环境:`macOS (Apple Silicon)`
5
5
  > 模型:`qwen/qwen3.6-flash` (DashScope)
6
- > AgentKit 版本:v0.7.3
6
+ > AgentKit 版本:v0.7.4
7
7
  > Thinking 模式:开启(默认)
8
8
  > LLM 调用模式:非流式(默认)
9
9
  > 缓存:开启(默认)
@@ -16,39 +16,39 @@
16
16
 
17
17
  | # | 示例 | 文件 | 耗时 | 状态 | 说明 |
18
18
  |---|------|------|-----:|:----:|------|
19
- | 1 | 01_basic_chat | `01_basic_chat.py` | 6.67s | ✅ | 运行通过 |
20
- | 2 | 02_tool_calling | `02_tool_calling.py` | 10.60s | ✅ | 运行通过 |
21
- | 3 | 03_skill_usage | `03_skill_usage.py` | 11.34s | ✅ | 运行通过 |
22
- | 4 | 03b_skill_tools_entry | `03b_skill_tools_entry.py` | 7.69s | ✅ | 运行通过 |
23
- | 5 | 04_multi_agent | `04_multi_agent.py` | 54.06s | ✅ | 运行通过 |
24
- | 6 | 05_guardrail | `05_guardrail.py` | 8.95s | ✅ | 运行通过 |
25
- | 7 | 05_human_in_the_loop | `05_human_in_the_loop.py` | 0.25s | ✅ | 运行通过 |
26
- | 8 | 06_orchestration | `06_orchestration.py` | 41.62s | ✅ | 运行通过 |
27
- | 9 | 07_sync_async_stream | `07_sync_async_stream.py` | 16.21s | ✅ | 运行通过 |
28
- | 10 | 08_memory | `08_memory.py` | 31.10s | ✅ | 运行通过 |
29
- | 11 | 08a_memory_simple_provider | `08a_memory_simple_provider.py` | 8.81s | ✅ | 运行通过 |
30
- | 12 | 08b_memory_mem0_provider | `08b_memory_mem0_provider.py` | 0.25s | ✅ | 运行通过 |
31
- | 13 | 08c_memory_file_provider | `08c_memory_file_provider.py` | 10.82s | ✅ | 运行通过 |
32
- | 14 | 09a_structured_data_sql | `09a_structured_data_sql.py` | 6.37s | ✅ | 运行通过 |
33
- | 15 | 09b_structured_data_graph | `09b_structured_data_graph.py` | 2.58s | ✅ | 运行通过 |
34
- | 16 | 09c_nebula_graph_tool | `09c_nebula_graph_tool.py` | 0.34s | ✅ | 运行通过 |
35
- | 17 | 10_skill_lifecycle | `10_skill_lifecycle.py` | 2.98s | ✅ | 运行通过 |
36
- | 18 | 11_orchestration_enhancement | `11_orchestration_enhancement.py` | 3.41s | ✅ | 运行通过 |
37
- | 19 | 12_run_context_serialization | `12_run_context_serialization.py` | 0.18s | ✅ | 运行通过 |
38
- | 20 | 13_human_in_the_loop | `13_human_in_the_loop.py` | 6.36s | ✅ | 运行通过 |
39
- | 21 | 14_event_standardization | `14_event_standardization.py` | 2.87s | ✅ | 运行通过 |
40
- | 22 | 15_multi_tenant_isolation | `15_multi_tenant_isolation.py` | 8.43s | ✅ | 运行通过 |
41
- | 23 | 16_lifecycle_hooks | `16_lifecycle_hooks.py` | 4.92s | ✅ | 运行通过 |
42
- | 24 | 17_checkpoint_handoff_resume | `17_checkpoint_handoff_resume.py` | 0.19s | ✅ | 运行通过 |
43
- | 25 | 18_model_cosplay | `18_model_cosplay.py` | 0.20s | ✅ | 运行通过 |
44
- | 26 | 19_hitl_deterministic | `19_hitl_deterministic.py` | 0.19s | ✅ | 运行通过 |
45
- | | **合计** | | **247.37s** | **26/26** | |
19
+ | 1 | 01_basic_chat | `01_basic_chat.py` | 5.63s | ✅ | 运行通过 |
20
+ | 2 | 02_tool_calling | `02_tool_calling.py` | 10.70s | ✅ | 运行通过 |
21
+ | 3 | 03_skill_usage | `03_skill_usage.py` | 15.31s | ✅ | 运行通过 |
22
+ | 4 | 03b_skill_tools_entry | `03b_skill_tools_entry.py` | 7.79s | ✅ | 运行通过 |
23
+ | 5 | 04_multi_agent | `04_multi_agent.py` | 39.61s | ✅ | 运行通过 |
24
+ | 6 | 05_guardrail | `05_guardrail.py` | 7.09s | ✅ | 运行通过 |
25
+ | 7 | 05_human_in_the_loop | `05_human_in_the_loop.py` | 0.31s | ✅ | 运行通过 |
26
+ | 8 | 06_orchestration | `06_orchestration.py` | 36.54s | ✅ | 运行通过 |
27
+ | 9 | 07_sync_async_stream | `07_sync_async_stream.py` | 21.68s | ✅ | 运行通过 |
28
+ | 10 | 08_memory | `08_memory.py` | 35.79s | ✅ | 运行通过 |
29
+ | 11 | 08a_memory_simple_provider | `08a_memory_simple_provider.py` | 9.05s | ✅ | 运行通过 |
30
+ | 12 | 08b_memory_mem0_provider | `08b_memory_mem0_provider.py` | 0.29s | ✅ | 运行通过 |
31
+ | 13 | 08c_memory_file_provider | `08c_memory_file_provider.py` | 11.53s | ✅ | 运行通过 |
32
+ | 14 | 09a_structured_data_sql | `09a_structured_data_sql.py` | 6.91s | ✅ | 运行通过 |
33
+ | 15 | 09b_structured_data_graph | `09b_structured_data_graph.py` | 5.44s | ✅ | 运行通过 |
34
+ | 16 | 09c_nebula_graph_tool | `09c_nebula_graph_tool.py` | 0.45s | ✅ | 运行通过 |
35
+ | 17 | 10_skill_lifecycle | `10_skill_lifecycle.py` | 2.38s | ✅ | 运行通过 |
36
+ | 18 | 11_orchestration_enhancement | `11_orchestration_enhancement.py` | 3.16s | ✅ | 运行通过 |
37
+ | 19 | 12_run_context_serialization | `12_run_context_serialization.py` | 0.23s | ✅ | 运行通过 |
38
+ | 20 | 13_human_in_the_loop | `13_human_in_the_loop.py` | 5.88s | ✅ | 运行通过 |
39
+ | 21 | 14_event_standardization | `14_event_standardization.py` | 3.84s | ✅ | 运行通过 |
40
+ | 22 | 15_multi_tenant_isolation | `15_multi_tenant_isolation.py` | 6.93s | ✅ | 运行通过 |
41
+ | 23 | 16_lifecycle_hooks | `16_lifecycle_hooks.py` | 6.70s | ✅ | 运行通过 |
42
+ | 24 | 17_checkpoint_handoff_resume | `17_checkpoint_handoff_resume.py` | 0.22s | ✅ | 运行通过 |
43
+ | 25 | 18_model_cosplay | `18_model_cosplay.py` | 0.26s | ✅ | 运行通过 |
44
+ | 26 | 19_hitl_deterministic | `19_hitl_deterministic.py` | 0.25s | ✅ | 运行通过 |
45
+ | | **合计** | | **243.97s** | **26/26** | |
46
46
 
47
47
  ## 耗时分析
48
48
 
49
- - **最快示例**:12_run_context_serialization (0.18s)
50
- - **最慢示例**:04_multi_agent (54.06s)
51
- - **性能改进**:本次批跑合计 247.37s,耗时主要集中在 04_multi_agent / 06_orchestration / 08_memory 等编排类示例。
49
+ - **最快示例**:17_checkpoint_handoff_resume (0.22s)
50
+ - **最慢示例**:04_multi_agent (39.61s)
51
+ - **性能改进**:本次批跑合计 243.97s,耗时主要集中在 04_multi_agent / 06_orchestration / 08_memory 等编排类示例。
52
52
 
53
53
  ## 已知问题
54
54
 
@@ -107,12 +107,21 @@ class LLMCache:
107
107
  tools: list[ToolDefinition] | None = None,
108
108
  ) -> Optional[LLMResponse]:
109
109
  """查询缓存,命中返回 LLMResponse,未命中返回 None"""
110
+ _, res = self.get_with_key(messages, tools)
111
+ return res
112
+
113
+ def get_with_key(
114
+ self,
115
+ messages: list[Message],
116
+ tools: list[ToolDefinition] | None = None,
117
+ ) -> tuple[str, Optional[LLMResponse]]:
118
+ """查询缓存并返回 (key, response_or_none)"""
110
119
  key = self._make_key(messages, tools)
111
120
  entry = self._cache.get(key)
112
121
 
113
122
  if entry is None:
114
123
  self._misses += 1
115
- return None
124
+ return key, None
116
125
 
117
126
  timestamp, response = entry
118
127
 
@@ -120,12 +129,12 @@ class LLMCache:
120
129
  if self._ttl > 0 and (time.time() - timestamp) > self._ttl:
121
130
  del self._cache[key]
122
131
  self._misses += 1
123
- return None
132
+ return key, None
124
133
 
125
134
  # 命中:移到末尾(LRU)
126
135
  self._cache.move_to_end(key)
127
136
  self._hits += 1
128
- return response
137
+ return key, response
129
138
 
130
139
  def put(
131
140
  self,
@@ -139,6 +148,13 @@ class LLMCache:
139
148
  return
140
149
 
141
150
  key = self._make_key(messages, tools)
151
+ self.put_with_key(key, response)
152
+
153
+ def put_with_key(self, key: str, response: LLMResponse) -> None:
154
+ """写入缓存(key 由外部预计算)。"""
155
+ if response.has_tool_calls:
156
+ return
157
+
142
158
  self._cache[key] = (time.time(), response)
143
159
  self._cache.move_to_end(key)
144
160
 
@@ -14,6 +14,7 @@ from typing import Any
14
14
 
15
15
  from .base import BaseLLM
16
16
  from .types import LLMConfig
17
+ from ..utils.env import load_env
17
18
 
18
19
 
19
20
  class LLMRegistry:
@@ -75,8 +76,23 @@ class LLMRegistry:
75
76
  def create_default(cls) -> BaseLLM:
76
77
  if cls._default_config:
77
78
  return cls.create(cls._default_config)
79
+ env_model = cls._get_default_model_from_env()
80
+ if env_model:
81
+ return cls.create(env_model)
78
82
  return cls.create(cls._default_model)
79
83
 
84
+ @classmethod
85
+ def _get_default_model_from_env(cls) -> str | None:
86
+ load_env()
87
+ for key in ("AGENTKIT_MODEL", "AGENTKIT_DEFAULT_MODEL", "AGENTKIT_STANDARD_MODEL", "AGENTKIT_OLLAMA_MODEL"):
88
+ value = (os.environ.get(key) or "").strip()
89
+ if not value:
90
+ continue
91
+ if key == "AGENTKIT_OLLAMA_MODEL" and not value.startswith("ollama/"):
92
+ value = f"ollama/{value}"
93
+ return value
94
+ return None
95
+
80
96
  # ------------------------------------------------------------------
81
97
 
82
98
  @classmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ni.agentkit
3
- Version: 0.7.3
3
+ Version: 0.7.4
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
@@ -155,8 +155,8 @@ python "$(python -c "import agentkit, os; print(os.path.join(agentkit.get_exampl
155
155
 
156
156
  ```bash
157
157
  dist/
158
- ├── ni_agentkit-0.7.3-py3-none-any.whl # pip install 用这个
159
- └── ni_agentkit-0.7.3.tar.gz # 源码分发
158
+ ├── ni_agentkit-0.7.4-py3-none-any.whl # pip install 用这个
159
+ └── ni_agentkit-0.7.4.tar.gz # 源码分发
160
160
  ```
161
161
 
162
162
  ## 📄 License
@@ -124,6 +124,7 @@ pyproject.toml
124
124
  ./tools/sqlite_tool.py
125
125
  ./tools/structured_data.py
126
126
  ./utils/__init__.py
127
+ ./utils/env.py
127
128
  ./utils/schema.py
128
129
  agents/__init__.py
129
130
  agents/agent.py
@@ -243,6 +244,7 @@ skills/loader.py
243
244
  skills/models.py
244
245
  skills/registry.py
245
246
  tests/test_after_callback.py
247
+ tests/test_default_model_env.py
246
248
  tests/test_graph_repository.py
247
249
  tests/test_quickstart_core.py
248
250
  tests/test_runner_checkpoint_resume.py
@@ -256,4 +258,5 @@ tools/skill_toolset.py
256
258
  tools/sqlite_tool.py
257
259
  tools/structured_data.py
258
260
  utils/__init__.py
261
+ utils/env.py
259
262
  utils/schema.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ni.agentkit"
3
- version = "0.7.3"
3
+ version = "0.7.4"
4
4
  description = "A Python-native Agent framework with first-class Skill support and multi-LLM adapter"
5
5
  authors = [
6
6
  { name = "Krix Tam", email = "krix.tam@qq.com" }
@@ -15,13 +15,20 @@ class SQLiteMemoryProvider(BaseMemoryProvider):
15
15
 
16
16
  def __init__(self, db_path: str = ".agentkit/rag/index.db") -> None:
17
17
  self._db_path = db_path
18
+ self._token_cache: dict[str, set[str]] = {}
18
19
 
19
20
  @staticmethod
20
21
  def _to_memory(record: dict, *, score: float = 0.0) -> Memory:
22
+ metadata = record.get("metadata", {})
23
+ if isinstance(metadata, str):
24
+ try:
25
+ metadata = json.loads(metadata or "{}")
26
+ except Exception:
27
+ metadata = {}
21
28
  return Memory(
22
29
  id=str(record["id"]),
23
30
  content=str(record["content"]),
24
- metadata=dict(record.get("metadata", {})),
31
+ metadata=dict(metadata),
25
32
  created_at=record.get("created_at"),
26
33
  score=score,
27
34
  )
@@ -48,14 +55,21 @@ class SQLiteMemoryProvider(BaseMemoryProvider):
48
55
  return [self._to_memory(record)]
49
56
 
50
57
  async def search(self, query, *, user_id=None, agent_id=None, limit=10):
51
- records = self._query_records(user_id=user_id, agent_id=agent_id)
58
+ records = self._query_records(user_id=user_id, agent_id=agent_id, decode_metadata=False)
52
59
  query_tokens = tokenize(str(query))
53
60
  query_set = set(query_tokens) if query_tokens else set(str(query))
54
61
  ranked: list[tuple[float, dict]] = []
55
62
  for record in records:
56
63
  content = str(record.get("content", ""))
57
- content_tokens = tokenize(content)
58
- content_set = set(content_tokens) if content_tokens else set(content)
64
+ cache_key = str(record.get("id", ""))
65
+ cached_set = self._token_cache.get(cache_key)
66
+ if cached_set is None:
67
+ content_tokens = tokenize(content)
68
+ cached_set = set(content_tokens) if content_tokens else set(content)
69
+ self._token_cache[cache_key] = cached_set
70
+ if len(self._token_cache) > 4096:
71
+ self._token_cache.clear()
72
+ content_set = cached_set
59
73
  overlap = len(query_set & content_set)
60
74
  if overlap <= 0:
61
75
  continue
@@ -73,7 +87,7 @@ class SQLiteMemoryProvider(BaseMemoryProvider):
73
87
  conn.commit()
74
88
  return cursor.rowcount > 0
75
89
 
76
- def _query_records(self, *, user_id=None, agent_id=None) -> list[dict]:
90
+ def _query_records(self, *, user_id=None, agent_id=None, decode_metadata: bool = True) -> list[dict]:
77
91
  clauses: list[str] = []
78
92
  params: list[str | None] = []
79
93
  if user_id is not None:
@@ -90,13 +104,25 @@ class SQLiteMemoryProvider(BaseMemoryProvider):
90
104
 
91
105
  with connect(self._db_path) as conn:
92
106
  rows = conn.execute(sql, params).fetchall()
107
+ if decode_metadata:
108
+ return [
109
+ {
110
+ "id": str(row["id"]),
111
+ "content": row["content"],
112
+ "user_id": row["user_id"],
113
+ "agent_id": row["agent_id"],
114
+ "metadata": json.loads(row["metadata"] or "{}"),
115
+ "created_at": row["created_at"],
116
+ }
117
+ for row in rows
118
+ ]
93
119
  return [
94
120
  {
95
121
  "id": str(row["id"]),
96
122
  "content": row["content"],
97
123
  "user_id": row["user_id"],
98
124
  "agent_id": row["agent_id"],
99
- "metadata": json.loads(row["metadata"] or "{}"),
125
+ "metadata": row["metadata"] or "{}",
100
126
  "created_at": row["created_at"],
101
127
  }
102
128
  for row in rows
@@ -3,17 +3,26 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import sqlite3
6
+ import threading
6
7
  from pathlib import Path
7
8
 
9
+ _schema_lock = threading.Lock()
10
+ _schema_initialized: set[str] = set()
11
+
8
12
 
9
13
  def connect(db_path: str) -> sqlite3.Connection:
10
14
  """创建启用外键与行字典访问的 SQLite 连接。"""
11
- path = Path(db_path)
15
+ path = Path(db_path).expanduser().resolve()
12
16
  path.parent.mkdir(parents=True, exist_ok=True)
13
17
  conn = sqlite3.connect(path)
14
18
  conn.row_factory = sqlite3.Row
15
19
  conn.execute("PRAGMA foreign_keys = ON")
16
- initialize_schema(conn)
20
+ key = str(path)
21
+ if key not in _schema_initialized:
22
+ with _schema_lock:
23
+ if key not in _schema_initialized:
24
+ initialize_schema(conn)
25
+ _schema_initialized.add(key)
17
26
  return conn
18
27
 
19
28
 
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from agentkit.llm.registry import LLMRegistry
6
+
7
+
8
+ def test_llmregistry_create_default_uses_env_from_dotenv(tmp_path: Path, monkeypatch):
9
+ monkeypatch.chdir(tmp_path)
10
+ (tmp_path / ".env").write_text("AGENTKIT_OLLAMA_MODEL=qwen3.5:cloud\n", encoding="utf-8")
11
+
12
+ llm = LLMRegistry.create_default()
13
+ assert llm.__class__.__name__ == "OllamaAdapter"
14
+ assert llm.config.model == "qwen3.5:cloud"
15
+
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ _NEAREST_DOTENV_CACHE: dict[str, Path | None] = {}
7
+ _DOTENV_LOAD_CACHE: dict[str, tuple[int, dict[str, str]]] = {}
8
+
9
+
10
+ def load_env(dotenv_path: str | None = None) -> dict[str, str]:
11
+ path = Path(dotenv_path).expanduser().resolve() if dotenv_path else _find_nearest_dotenv()
12
+ if path is None or not path.exists() or not path.is_file():
13
+ return {}
14
+
15
+ stat = path.stat()
16
+ cache_key = str(path)
17
+ cached = _DOTENV_LOAD_CACHE.get(cache_key)
18
+ if cached and cached[0] == stat.st_mtime_ns:
19
+ loaded = cached[1]
20
+ for k, v in loaded.items():
21
+ if k not in os.environ:
22
+ os.environ[k] = v
23
+ return dict(loaded)
24
+
25
+ loaded: dict[str, str] = {}
26
+ for raw in path.read_text(encoding="utf-8").splitlines():
27
+ line = raw.strip()
28
+ if not line or line.startswith("#"):
29
+ continue
30
+ if line.startswith("export "):
31
+ line = line[len("export ") :].strip()
32
+ if "=" not in line:
33
+ continue
34
+
35
+ key, value = line.split("=", 1)
36
+ key = key.strip()
37
+ if not key:
38
+ continue
39
+
40
+ value = value.strip()
41
+ if (value.startswith('"') and value.endswith('"')) or (value.startswith("'") and value.endswith("'")):
42
+ value = value[1:-1]
43
+
44
+ if key not in os.environ:
45
+ os.environ[key] = value
46
+ loaded[key] = value
47
+
48
+ _DOTENV_LOAD_CACHE[cache_key] = (stat.st_mtime_ns, dict(loaded))
49
+ return loaded
50
+
51
+
52
+ def _find_nearest_dotenv() -> Path | None:
53
+ cwd = Path.cwd().resolve()
54
+ cached = _NEAREST_DOTENV_CACHE.get(str(cwd))
55
+ if cached is not None:
56
+ return cached
57
+
58
+ candidates: list[Path] = []
59
+ for base in [cwd, *cwd.parents]:
60
+ candidates.append(base / ".env")
61
+ candidates.append(base / ".evn")
62
+
63
+ seen: set[Path] = set()
64
+ for path in candidates:
65
+ if path in seen:
66
+ continue
67
+ seen.add(path)
68
+ if path.exists() and path.is_file():
69
+ _NEAREST_DOTENV_CACHE[str(cwd)] = path
70
+ return path
71
+ _NEAREST_DOTENV_CACHE[str(cwd)] = None
72
+ return None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes