ni.agentkit 0.6.0__tar.gz → 0.6.2__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.
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/PKG-INFO +3 -3
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/README.md +2 -2
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/__init__.py +1 -1
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/agents/agent.py +44 -7
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/agents/base_agent.py +31 -22
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/agents/orchestrators.py +18 -14
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/docs/Architecture.md +8 -5
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/docs/README.md +1 -1
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/docs/Reference.md +6 -4
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/docs/TestReport.md +25 -25
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/17_checkpoint_handoff_resume.py +11 -3
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/17_checkpoint_handoff_resume.py +11 -4
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/ni.agentkit.egg-info/PKG-INFO +3 -3
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/ni.agentkit.egg-info/SOURCES.txt +3 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/pyproject.toml +1 -1
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/runner/context.py +63 -2
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/runner/runner.py +82 -9
- ni_agentkit-0.6.2/tests/test_after_callback.py +69 -0
- ni_agentkit-0.6.2/tests/test_quickstart_core.py +125 -0
- ni_agentkit-0.6.2/tests/test_runner_checkpoint_resume.py +152 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/tools/skill_toolset.py +4 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/LICENSE +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/_cli.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/agents/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/docs/QuickStart.md +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/01_basic_chat.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/02_tool_calling.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/03_skill_usage.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/04_multi_agent.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/05_guardrail.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/06_orchestration.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/07_sync_async_stream.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/08_memory.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/09a_structured_data_sql.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/09b_structured_data_graph.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/10_skill_lifecycle.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/11_orchestration_enhancement.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/12_run_context_serialization.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/13_human_in_the_loop.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/14_event_standardization.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/15_multi_tenant_isolation.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/16_lifecycle_hooks.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/18_model_cosplay.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/README.md +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/ollama/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/quickstart.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/01_basic_chat.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/02_tool_calling.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/03_skill_usage.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/04_multi_agent.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/05_guardrail.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/06_orchestration.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/07_sync_async_stream.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/08_memory.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/09a_structured_data_sql.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/09b_structured_data_graph.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/10_skill_lifecycle.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/11_orchestration_enhancement.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/12_run_context_serialization.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/13_human_in_the_loop.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/14_event_standardization.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/15_multi_tenant_isolation.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/16_lifecycle_hooks.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/18_model_cosplay.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/README.md +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/standard/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/examples/test_ollama.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/adapters/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/adapters/anthropic_adapter.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/adapters/google_adapter.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/adapters/ollama_adapter.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/adapters/openai_adapter.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/adapters/openai_compatible.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/base.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/cache.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/middleware.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/registry.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/llm/types.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/memory/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/memory/base.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/memory/mem0_provider.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/ni.agentkit.egg-info/dependency_links.txt +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/ni.agentkit.egg-info/entry_points.txt +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/ni.agentkit.egg-info/requires.txt +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/ni.agentkit.egg-info/top_level.txt +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/runner/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/runner/context_store.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/runner/events.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/safety/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/safety/guardrails.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/safety/permissions.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/setup.cfg +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/skills/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/skills/loader.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/skills/models.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/skills/registry.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/tools/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/tools/base_tool.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/tools/function_tool.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/tools/nebula_tool.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/tools/sqlite_tool.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/tools/structured_data.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/utils/__init__.py +0 -0
- {ni_agentkit-0.6.0 → ni_agentkit-0.6.2}/utils/schema.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ni.agentkit
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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.
|
|
157
|
-
└── ni_agentkit-0.6.
|
|
156
|
+
├── ni_agentkit-0.6.2-py3-none-any.whl # pip install 用这个
|
|
157
|
+
└── ni_agentkit-0.6.2.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.
|
|
115
|
-
└── ni_agentkit-0.6.
|
|
114
|
+
├── ni_agentkit-0.6.2-py3-none-any.whl # pip install 用这个
|
|
115
|
+
└── ni_agentkit-0.6.2.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.
|
|
34
|
+
__version__ = "0.6.2"
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def get_docs_dir() -> str:
|
|
@@ -190,6 +190,7 @@ class Agent(BaseAgent):
|
|
|
190
190
|
llm = self._resolve_model()
|
|
191
191
|
memory_injection = ""
|
|
192
192
|
skill_prompt_injection = ""
|
|
193
|
+
runtime_skill_toolset: SkillToolset | None = None
|
|
193
194
|
history_messages: list[Message] = []
|
|
194
195
|
parsed_history_len = 0
|
|
195
196
|
last_tool_signature: tuple[tuple[str, int], ...] = tuple()
|
|
@@ -206,8 +207,8 @@ class Agent(BaseAgent):
|
|
|
206
207
|
logger.warning("检索记忆失败: %s", e)
|
|
207
208
|
|
|
208
209
|
if self.skills:
|
|
209
|
-
|
|
210
|
-
skill_prompt_injection = "\n\n" +
|
|
210
|
+
runtime_skill_toolset = SkillToolset(skills=self.skills)
|
|
211
|
+
skill_prompt_injection = "\n\n" + runtime_skill_toolset.get_system_prompt_injection()
|
|
211
212
|
|
|
212
213
|
try:
|
|
213
214
|
while round_count < self.max_tool_rounds:
|
|
@@ -219,7 +220,32 @@ class Agent(BaseAgent):
|
|
|
219
220
|
instructions += skill_prompt_injection
|
|
220
221
|
|
|
221
222
|
# 2. 获取工具
|
|
222
|
-
tools =
|
|
223
|
+
tools: list[BaseTool] = []
|
|
224
|
+
for tool_union in self.tools:
|
|
225
|
+
if isinstance(tool_union, BaseTool):
|
|
226
|
+
tools.append(tool_union)
|
|
227
|
+
elif isinstance(tool_union, BaseToolset):
|
|
228
|
+
tools.extend(await tool_union.get_tools(ctx))
|
|
229
|
+
elif callable(tool_union):
|
|
230
|
+
fn_key = id(tool_union)
|
|
231
|
+
cached_tool = self._callable_tool_cache.get(fn_key)
|
|
232
|
+
if cached_tool is None:
|
|
233
|
+
cached_tool = FunctionTool.from_function(tool_union)
|
|
234
|
+
self._callable_tool_cache[fn_key] = cached_tool
|
|
235
|
+
tools.append(cached_tool)
|
|
236
|
+
|
|
237
|
+
if runtime_skill_toolset is not None:
|
|
238
|
+
runtime_skill_toolset.set_additional_tools(tools.copy())
|
|
239
|
+
tools.extend(await runtime_skill_toolset.get_tools(ctx))
|
|
240
|
+
|
|
241
|
+
for target in self.handoffs:
|
|
242
|
+
if isinstance(target, BaseAgent):
|
|
243
|
+
target_key = id(target)
|
|
244
|
+
cached_handoff_tool = self._handoff_tool_cache.get(target_key)
|
|
245
|
+
if cached_handoff_tool is None:
|
|
246
|
+
cached_handoff_tool = self._create_handoff_tool(target)
|
|
247
|
+
self._handoff_tool_cache[target_key] = cached_handoff_tool
|
|
248
|
+
tools.append(cached_handoff_tool)
|
|
223
249
|
tool_signature = tuple((tool.name, id(tool)) for tool in tools)
|
|
224
250
|
tool_defs_start = time.perf_counter()
|
|
225
251
|
if tool_signature != last_tool_signature:
|
|
@@ -388,13 +414,24 @@ class Agent(BaseAgent):
|
|
|
388
414
|
try:
|
|
389
415
|
result = await tool.execute(ctx, tool_call.arguments)
|
|
390
416
|
except HumanInputRequested as e:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
417
|
+
suspension = ctx.register_suspension(
|
|
418
|
+
tool_call_id=tool_call.id,
|
|
419
|
+
tool_name=tool_call.name,
|
|
420
|
+
prompt=e.prompt,
|
|
421
|
+
form_schema=e.kwargs.get("form_schema"),
|
|
422
|
+
resume_strategy=e.kwargs.get("resume_strategy", "as_tool_result"),
|
|
423
|
+
)
|
|
394
424
|
yield Event(
|
|
395
425
|
agent=self.name,
|
|
396
426
|
type=EventType.SUSPEND_REQUESTED,
|
|
397
|
-
data={
|
|
427
|
+
data={
|
|
428
|
+
"suspension_id": suspension.suspension_id,
|
|
429
|
+
"prompt": e.prompt,
|
|
430
|
+
"tool": tool_call.name,
|
|
431
|
+
"tool_call_id": tool_call.id,
|
|
432
|
+
"resume_strategy": suspension.resume_strategy,
|
|
433
|
+
**e.kwargs,
|
|
434
|
+
},
|
|
398
435
|
)
|
|
399
436
|
return
|
|
400
437
|
except Exception as e:
|
|
@@ -5,7 +5,9 @@ agentkit/agents/base_agent.py — 所有 Agent 的基类
|
|
|
5
5
|
"""
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import asyncio
|
|
8
9
|
from abc import abstractmethod
|
|
10
|
+
from contextlib import aclosing
|
|
9
11
|
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Optional
|
|
10
12
|
|
|
11
13
|
from pydantic import BaseModel, ConfigDict, Field
|
|
@@ -62,30 +64,37 @@ class BaseAgent(BaseModel):
|
|
|
62
64
|
|
|
63
65
|
async def run(self, ctx: "RunContext") -> AsyncGenerator[Event, None]:
|
|
64
66
|
"""运行入口 — 子类不可覆盖"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if
|
|
69
|
-
|
|
70
|
-
if
|
|
67
|
+
emit_after_events = True
|
|
68
|
+
try:
|
|
69
|
+
# 1. before callback
|
|
70
|
+
if self.before_agent_callback:
|
|
71
|
+
result, duration, err = await self._run_hook(self.before_agent_callback, "before_agent_callback", ctx)
|
|
72
|
+
if err:
|
|
73
|
+
yield Event(agent=self.name, type="error", data={"hook": "before_agent", "error": str(err), "duration": duration})
|
|
74
|
+
if self.fail_fast_on_hook_error:
|
|
75
|
+
return
|
|
76
|
+
elif result is not None:
|
|
77
|
+
yield Event(agent=self.name, type="callback", data={"result": result, "duration": duration})
|
|
71
78
|
return
|
|
72
|
-
elif result is not None:
|
|
73
|
-
yield Event(agent=self.name, type="callback", data={"result": result, "duration": duration})
|
|
74
|
-
return
|
|
75
|
-
|
|
76
|
-
# 2. 核心逻辑(子类实现)
|
|
77
|
-
async for event in self._run_impl(ctx):
|
|
78
|
-
yield event
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
# 2. 核心逻辑(子类实现)
|
|
81
|
+
impl_stream = self._run_impl(ctx)
|
|
82
|
+
async with aclosing(impl_stream):
|
|
83
|
+
async for event in impl_stream:
|
|
84
|
+
yield event
|
|
85
|
+
except (GeneratorExit, asyncio.CancelledError):
|
|
86
|
+
# 外部提前中断时不再尝试向外 yield 事件,但必须执行 after 回调逻辑
|
|
87
|
+
emit_after_events = False
|
|
88
|
+
raise
|
|
89
|
+
finally:
|
|
90
|
+
# 3. after callback(保证执行)
|
|
91
|
+
if self.after_agent_callback:
|
|
92
|
+
result, duration, err = await self._run_hook(self.after_agent_callback, "after_agent_callback", ctx)
|
|
93
|
+
if emit_after_events:
|
|
94
|
+
if err:
|
|
95
|
+
yield Event(agent=self.name, type="error", data={"hook": "after_agent", "error": str(err), "duration": duration})
|
|
96
|
+
elif result is not None:
|
|
97
|
+
yield Event(agent=self.name, type="callback", data={"result": result, "duration": duration})
|
|
89
98
|
|
|
90
99
|
@abstractmethod
|
|
91
100
|
async def _run_impl(self, ctx: "RunContext") -> AsyncGenerator[Event, None]:
|
|
@@ -8,6 +8,7 @@ agentkit/agents/loop_agent.py — 循环执行子 Agent
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
|
+
from contextlib import aclosing
|
|
11
12
|
from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable
|
|
12
13
|
|
|
13
14
|
from ..runner.events import Event
|
|
@@ -22,10 +23,11 @@ class SequentialAgent(BaseAgent):
|
|
|
22
23
|
|
|
23
24
|
async def _run_impl(self, ctx: "RunContext") -> AsyncGenerator[Event, None]:
|
|
24
25
|
for sub in self.sub_agents:
|
|
25
|
-
async
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
async with aclosing(sub.run(ctx)) as stream:
|
|
27
|
+
async for event in stream:
|
|
28
|
+
yield event
|
|
29
|
+
if event.type == "escalate":
|
|
30
|
+
return
|
|
29
31
|
|
|
30
32
|
|
|
31
33
|
class ParallelAgent(BaseAgent):
|
|
@@ -60,12 +62,13 @@ class ParallelAgent(BaseAgent):
|
|
|
60
62
|
async def run_branch(agent: BaseAgent, branch_ctx: "RunContext") -> None:
|
|
61
63
|
branch_status[agent.name] = "running"
|
|
62
64
|
try:
|
|
63
|
-
async
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
async with aclosing(agent.run(branch_ctx)) as stream:
|
|
66
|
+
async for event in stream:
|
|
67
|
+
all_events[agent.name].append(event)
|
|
68
|
+
await queue.put((agent.name, event))
|
|
69
|
+
if event.type == "escalate":
|
|
70
|
+
branch_status[agent.name] = "escalated"
|
|
71
|
+
return
|
|
69
72
|
branch_status[agent.name] = "completed"
|
|
70
73
|
except asyncio.CancelledError:
|
|
71
74
|
branch_status[agent.name] = "cancelled"
|
|
@@ -112,10 +115,11 @@ class LoopAgent(BaseAgent):
|
|
|
112
115
|
async def _run_impl(self, ctx: "RunContext") -> AsyncGenerator[Event, None]:
|
|
113
116
|
for iteration in range(self.max_iterations):
|
|
114
117
|
for sub in self.sub_agents:
|
|
115
|
-
async
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
async with aclosing(sub.run(ctx)) as stream:
|
|
119
|
+
async for event in stream:
|
|
120
|
+
yield event
|
|
121
|
+
if event.type == "escalate":
|
|
122
|
+
return
|
|
119
123
|
|
|
120
124
|
if self.loop_condition is not None:
|
|
121
125
|
# Provide ctx and current iteration index (0-based) as state
|
|
@@ -157,6 +157,8 @@ Runner.run()
|
|
|
157
157
|
|
|
158
158
|
**9 个回调点**标记为 ❶ ~ ❾,覆盖了 Agent、Model、Tool、Handoff 四个层面的前后拦截及错误处理(含 `on_error_callback`)。
|
|
159
159
|
|
|
160
|
+
补充说明:`after_agent_callback` 通过 `finally` 语义保证执行。即使上游编排发生提前 `return`(如 `escalate`)或外部关闭流式生成器(`aclose`),也会触发该回调;但在生成器关闭路径下,不保证回调事件继续对外 `yield`。
|
|
161
|
+
|
|
160
162
|
---
|
|
161
163
|
|
|
162
164
|
## Human-in-the-loop (HITL) 与状态管理
|
|
@@ -170,18 +172,19 @@ AgentKit 原生支持在任务执行过程中安全地挂起(Suspend)并持
|
|
|
170
172
|
│
|
|
171
173
|
├─ 遇到需要人工确认的工具 (抛出 HumanInputRequested)
|
|
172
174
|
│
|
|
173
|
-
├─
|
|
175
|
+
├─ Agent 捕获异常,注册 SuspensionRecord 并触发 suspend_requested 事件(含 suspension_id)
|
|
174
176
|
│
|
|
175
|
-
├─ ContextStore 保存当前 RunContext (
|
|
177
|
+
├─ ContextStore 保存当前 RunContext (包含会话状态、执行分支、suspensions、resume_idempotency 等)
|
|
176
178
|
│
|
|
177
179
|
▼
|
|
178
180
|
[进程可完全退出 / 释放资源]
|
|
179
181
|
│
|
|
180
182
|
├─ 外部人工提供输入
|
|
181
183
|
│
|
|
182
|
-
├─ Runner.resume(session_id, user_input, context_store)
|
|
184
|
+
├─ Runner.resume(session_id, user_input, context_store, suspension_id=None, idempotency_key=None)
|
|
183
185
|
│
|
|
184
|
-
├─ ContextStore 恢复 RunContext
|
|
186
|
+
├─ ContextStore 恢复 RunContext,按 suspension_id(或最新 pending)定位挂起点
|
|
187
|
+
│ └─ resume_strategy=as_tool_result 时将 user_input 注入对应 tool_call 结果
|
|
185
188
|
│
|
|
186
189
|
▼
|
|
187
190
|
[Agent 恢复运行,继续未完成的对话与工具链]
|
|
@@ -540,7 +543,7 @@ agent = Agent(memory=my_memory, memory_async_write=False, ...)
|
|
|
540
543
|
|
|
541
544
|
#### 生命周期 Hooks / Callbacks
|
|
542
545
|
AgentKit 提供细粒度的执行拦截点,支持同步/异步回调,并允许请求与响应改写:
|
|
543
|
-
- **`before_agent_callback` / `after_agent_callback`**: 拦截整个 Agent
|
|
546
|
+
- **`before_agent_callback` / `after_agent_callback`**: 拦截整个 Agent 会话。`after_agent_callback` 在提前中断/外部关闭流的场景下也会执行(收尾保证),但关闭路径不保证继续外发 callback 事件。
|
|
544
547
|
- **`before_model_callback` / `after_model_callback`**: 在 LLM 调用前后拦截,允许改写 Prompt 和模型输出。
|
|
545
548
|
- **`before_tool_callback` / `after_tool_callback`**: 在工具执行前后拦截,允许改写执行结果。
|
|
546
549
|
- **`before_handoff_callback` / `after_handoff_callback`**: 在触发 Handoff 转移前拦截,可动态修改目标 Agent。
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Python 原生的 Agent 开发框架,内置一等公民级别的 Skill 支持和自研多模型适配层。
|
|
4
4
|
|
|
5
5
|
[](https://python.org)
|
|
6
|
-
[]()
|
|
7
7
|
[]()
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -72,7 +72,7 @@ from agentkit import Agent
|
|
|
72
72
|
| `memory_async_write` | `bool` | `True` | 记忆写入模式。`True`=fire-and-forget 异步写入(不阻塞返回);`False`=同步等待写入完成(多轮串行对话推荐) |
|
|
73
73
|
| `model_cosplay_enabled` | `bool` | `False` | 是否开启 ModelCosplay。关闭时如果类已预设 `model`,实例化时不允许覆盖;开启后允许实例化和运行时改写模型 |
|
|
74
74
|
| `before_agent_callback` | `Callable \| None` | `None` | Agent 运行前回调 |
|
|
75
|
-
| `after_agent_callback` | `Callable \| None` | `None` | Agent
|
|
75
|
+
| `after_agent_callback` | `Callable \| None` | `None` | Agent 运行后回调。通过 `finally` 语义保证执行;即使上游提前中断或外部关闭流(`aclose`)也会触发,但关闭路径不保证继续外发 callback 事件 |
|
|
76
76
|
| `before_model_callback` | `Callable \| None` | `None` | LLM 调用前回调(可拦截/改写) |
|
|
77
77
|
| `after_model_callback` | `Callable \| None` | `None` | LLM 调用后回调(可改写响应) |
|
|
78
78
|
| `before_tool_callback` | `Callable \| None` | `None` | 工具调用前回调(可拦截执行) |
|
|
@@ -116,7 +116,7 @@ agent = Agent(
|
|
|
116
116
|
| `description` | `str` | 描述 |
|
|
117
117
|
| `sub_agents` | `list[BaseAgent]` | 子 Agent(自动建立父子关系) |
|
|
118
118
|
| `before_agent_callback` | `Callable \| None` | 运行前回调 |
|
|
119
|
-
| `after_agent_callback` | `Callable \| None` |
|
|
119
|
+
| `after_agent_callback` | `Callable \| None` | 运行后回调。支持提前中断/流关闭场景下的收尾触发(关闭路径不保证事件外发) |
|
|
120
120
|
| `model_cosplay_enabled` | `bool` | 是否开启 ModelCosplay(默认 `False`) |
|
|
121
121
|
|
|
122
122
|
**子类化**:
|
|
@@ -181,7 +181,7 @@ from agentkit import Runner
|
|
|
181
181
|
| `run_sync` | `(agent, **kwargs) -> RunResult` | 同步运行(内部调用 asyncio.run) |
|
|
182
182
|
| `run_streamed` | `async (agent, *, input, user_id=None, session_id=None, **kwargs) -> AsyncGenerator[Event, None]` | 流式运行,实时产出 Event |
|
|
183
183
|
| `run_with_checkpoint` | `async (agent, *, input, session_id, context_store, ..., max_turns=10) -> AsyncGenerator[Event, None]` | 流式运行(支持挂起),遇到 `suspend_requested` 时自动保存上下文与执行指针(turn/current_agent/agent_path),并追加发出 `suspended` 事件 |
|
|
184
|
-
| `resume` | `async (agent, *, session_id, user_input, context_store,
|
|
184
|
+
| `resume` | `async (agent, *, session_id, user_input, context_store, ..., suspension_id=None, idempotency_key=None) -> AsyncGenerator[Event, None]` | 恢复被挂起会话;优先按 checkpoint 的 `agent_path` 定位恢复节点,再按 `suspension_id`(或最新 pending)恢复挂起点;支持 `idempotency_key` 幂等 |
|
|
185
185
|
|
|
186
186
|
**参数**:
|
|
187
187
|
|
|
@@ -211,7 +211,9 @@ from agentkit.runner.context import RunContext
|
|
|
211
211
|
| `user_id` | `str \| None` | 用户 ID(用于多租户和记忆隔离) |
|
|
212
212
|
| `session_id` | `str` | 会话 ID,默认随机生成(用于断点续跑和上下文隔离) |
|
|
213
213
|
| `messages` | `list[dict]` | 当前完整的对话消息链 |
|
|
214
|
-
| `state` | `dict` |
|
|
214
|
+
| `state` | `dict` | 运行期间的内部状态(不再承载挂起协议字段) |
|
|
215
|
+
| `suspensions` | `list[SuspensionRecord]` | 框架托管的挂起记录列表(含 `suspension_id/tool_call_id/tool_name/prompt/form_schema/resume_strategy`) |
|
|
216
|
+
| `resume_idempotency` | `dict[str, dict]` | resume 幂等记录,避免重复提交人工输入 |
|
|
215
217
|
|
|
216
218
|
**方法**:
|
|
217
219
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# AgentKit 示例测试报告
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
>
|
|
3
|
+
> 测试时间:`2026-04-22`
|
|
4
|
+
> 测试环境:`macOS (Apple Silicon)`
|
|
5
5
|
> 模型:Ollama `qwen3.5:cloud`
|
|
6
|
-
> AgentKit 版本:v0.6.
|
|
6
|
+
> AgentKit 版本:v0.6.2
|
|
7
7
|
> Thinking 模式:开启(默认)
|
|
8
8
|
> LLM 调用模式:非流式(默认)
|
|
9
9
|
> 缓存:开启(默认)
|
|
@@ -15,31 +15,31 @@
|
|
|
15
15
|
|
|
16
16
|
| # | 示例 | 文件 | 耗时 | 状态 | 说明 |
|
|
17
17
|
|---|------|------|-----:|:----:|------|
|
|
18
|
-
| 1 | 基础对话 | `01_basic_chat.py` |
|
|
19
|
-
| 2 | 工具调用 | `02_tool_calling.py` |
|
|
20
|
-
| 3 | Skill 使用 | `03_skill_usage.py` |
|
|
21
|
-
| 4 | 多 Agent 协作 | `04_multi_agent.py` |
|
|
22
|
-
| 5 | 安全护栏 | `05_guardrail.py` |
|
|
23
|
-
| 6 | 编排 Agent | `06_orchestration.py` |
|
|
24
|
-
| 7 | 同步/异步/流式 | `07_sync_async_stream.py` |
|
|
25
|
-
| 8 | 记忆系统 | `08_memory.py` |
|
|
26
|
-
| 9A | 结构化数据(SQL) | `09a_structured_data_sql.py` |
|
|
27
|
-
| 9B | 结构化数据(图) | `09b_structured_data_graph.py` |
|
|
28
|
-
| 10 | Skill 生命周期 | `10_skill_lifecycle.py` |
|
|
29
|
-
| 11 | 编排增强 | `11_orchestration_enhancement.py` |
|
|
30
|
-
| 12 | 序列化协议 | `12_run_context_serialization.py` | 0.
|
|
31
|
-
| 13 | Human in the Loop | `13_human_in_the_loop.py` |
|
|
32
|
-
| 14 | Event 标准化 | `14_event_standardization.py` |
|
|
33
|
-
| 15 | 多租户隔离 | `15_multi_tenant_isolation.py` | 0.
|
|
34
|
-
| 16 | 生命周期 Hooks | `16_lifecycle_hooks.py` | 0.
|
|
35
|
-
| 17 | Checkpoint + Handoff + Resume | `17_checkpoint_handoff_resume.py` | 0.
|
|
36
|
-
| 18 | ModelCosplay | `18_model_cosplay.py` | 0.
|
|
37
|
-
| | **合计** | | **
|
|
18
|
+
| 1 | 基础对话 | `01_basic_chat.py` | 124.36s | ✅ | 运行通过 |
|
|
19
|
+
| 2 | 工具调用 | `02_tool_calling.py` | 30.32s | ✅ | 运行通过 |
|
|
20
|
+
| 3 | Skill 使用 | `03_skill_usage.py` | 258.00s | ✅ | 运行通过(本轮最慢) |
|
|
21
|
+
| 4 | 多 Agent 协作 | `04_multi_agent.py` | 183.07s | ✅ | 运行通过 |
|
|
22
|
+
| 5 | 安全护栏 | `05_guardrail.py` | 21.82s | ✅ | 运行通过 |
|
|
23
|
+
| 6 | 编排 Agent | `06_orchestration.py` | 156.18s | ✅ | 运行通过 |
|
|
24
|
+
| 7 | 同步/异步/流式 | `07_sync_async_stream.py` | 17.57s | ✅ | 运行通过 |
|
|
25
|
+
| 8 | 记忆系统 | `08_memory.py` | 148.30s | ✅ | 运行通过 |
|
|
26
|
+
| 9A | 结构化数据(SQL) | `09a_structured_data_sql.py` | 3.99s | ✅ | 运行通过 |
|
|
27
|
+
| 9B | 结构化数据(图) | `09b_structured_data_graph.py` | 8.48s | ✅ | 运行通过 |
|
|
28
|
+
| 10 | Skill 生命周期 | `10_skill_lifecycle.py` | 1.77s | ✅ | 运行通过 |
|
|
29
|
+
| 11 | 编排增强 | `11_orchestration_enhancement.py` | 86.13s | ✅ | 运行通过 |
|
|
30
|
+
| 12 | 序列化协议 | `12_run_context_serialization.py` | 0.31s | ✅ | 运行通过 |
|
|
31
|
+
| 13 | Human in the Loop | `13_human_in_the_loop.py` | 6.21s | ✅ | 运行通过 |
|
|
32
|
+
| 14 | Event 标准化 | `14_event_standardization.py` | 50.10s | ✅ | 运行通过 |
|
|
33
|
+
| 15 | 多租户隔离 | `15_multi_tenant_isolation.py` | 0.44s | ✅ | 运行通过 |
|
|
34
|
+
| 16 | 生命周期 Hooks | `16_lifecycle_hooks.py` | 0.27s | ✅ | 运行通过 |
|
|
35
|
+
| 17 | Checkpoint + Handoff + Resume | `17_checkpoint_handoff_resume.py` | 0.17s | ✅ | 运行通过(本轮最快) |
|
|
36
|
+
| 18 | ModelCosplay | `18_model_cosplay.py` | 0.18s | ✅ | 运行通过 |
|
|
37
|
+
| | **合计** | | **1097.67s** | **19/19** | |
|
|
38
38
|
|
|
39
39
|
## 耗时分析
|
|
40
40
|
|
|
41
|
-
- **最快示例**:
|
|
42
|
-
- **最慢示例**:
|
|
41
|
+
- **最快示例**:17 Checkpoint + Handoff + Resume(0.17s)
|
|
42
|
+
- **最慢示例**:3 Skill 使用(258.00s)
|
|
43
43
|
- **耗时集中区间**:涉及多轮推理/记忆写入/编排循环的示例耗时显著更高
|
|
44
44
|
|
|
45
45
|
## 各示例 LLM 调用次数估算
|
|
@@ -22,12 +22,20 @@ class ReviewAgent(BaseAgent):
|
|
|
22
22
|
async def _run_impl(self, ctx: RunContext) -> AsyncGenerator[Event, None]:
|
|
23
23
|
if not ctx.state.get("review_suspended_once"):
|
|
24
24
|
ctx.state["review_suspended_once"] = True
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
suspension = ctx.register_suspension(
|
|
26
|
+
tool_call_id="manual-review-1",
|
|
27
|
+
tool_name="manual_review",
|
|
28
|
+
prompt="请审批该任务(approve/reject)",
|
|
29
|
+
)
|
|
27
30
|
yield Event(
|
|
28
31
|
agent=self.name,
|
|
29
32
|
type=EventType.SUSPEND_REQUESTED,
|
|
30
|
-
data={
|
|
33
|
+
data={
|
|
34
|
+
"suspension_id": suspension.suspension_id,
|
|
35
|
+
"prompt": "请审批该任务(approve/reject)",
|
|
36
|
+
"tool": "manual_review",
|
|
37
|
+
"tool_call_id": "manual-review-1",
|
|
38
|
+
},
|
|
31
39
|
)
|
|
32
40
|
return
|
|
33
41
|
|
|
@@ -23,13 +23,20 @@ class ReviewAgent(BaseAgent):
|
|
|
23
23
|
# 第一次进入:请求人工输入并挂起
|
|
24
24
|
if not ctx.state.get("review_suspended_once"):
|
|
25
25
|
ctx.state["review_suspended_once"] = True
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
suspension = ctx.register_suspension(
|
|
27
|
+
tool_call_id="manual-review-1",
|
|
28
|
+
tool_name="manual_review",
|
|
29
|
+
prompt="请审批该任务(approve/reject)",
|
|
30
|
+
)
|
|
29
31
|
yield Event(
|
|
30
32
|
agent=self.name,
|
|
31
33
|
type=EventType.SUSPEND_REQUESTED,
|
|
32
|
-
data={
|
|
34
|
+
data={
|
|
35
|
+
"suspension_id": suspension.suspension_id,
|
|
36
|
+
"prompt": "请审批该任务(approve/reject)",
|
|
37
|
+
"tool": "manual_review",
|
|
38
|
+
"tool_call_id": "manual-review-1",
|
|
39
|
+
},
|
|
33
40
|
)
|
|
34
41
|
return
|
|
35
42
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ni.agentkit
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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.
|
|
157
|
-
└── ni_agentkit-0.6.
|
|
156
|
+
├── ni_agentkit-0.6.2-py3-none-any.whl # pip install 用这个
|
|
157
|
+
└── ni_agentkit-0.6.2.tar.gz # 源码分发
|
|
158
158
|
```
|
|
159
159
|
|
|
160
160
|
## 📄 License
|
|
@@ -182,6 +182,9 @@ skills/__init__.py
|
|
|
182
182
|
skills/loader.py
|
|
183
183
|
skills/models.py
|
|
184
184
|
skills/registry.py
|
|
185
|
+
tests/test_after_callback.py
|
|
186
|
+
tests/test_quickstart_core.py
|
|
187
|
+
tests/test_runner_checkpoint_resume.py
|
|
185
188
|
tools/__init__.py
|
|
186
189
|
tools/base_tool.py
|
|
187
190
|
tools/function_tool.py
|
|
@@ -6,12 +6,27 @@ from __future__ import annotations
|
|
|
6
6
|
import copy
|
|
7
7
|
import json
|
|
8
8
|
import logging
|
|
9
|
-
|
|
9
|
+
import time
|
|
10
|
+
from dataclasses import asdict, dataclass, field
|
|
10
11
|
from typing import Any, Optional
|
|
11
12
|
from uuid import uuid4
|
|
12
13
|
|
|
13
14
|
logger = logging.getLogger(__name__)
|
|
14
15
|
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class SuspensionRecord:
|
|
19
|
+
suspension_id: str
|
|
20
|
+
tool_call_id: str
|
|
21
|
+
tool_name: str
|
|
22
|
+
prompt: str
|
|
23
|
+
form_schema: dict[str, Any] | None = None
|
|
24
|
+
resume_strategy: str = "as_tool_result"
|
|
25
|
+
created_at: float = field(default_factory=time.time)
|
|
26
|
+
resolved_at: float | None = None
|
|
27
|
+
resolved_input: str | None = None
|
|
28
|
+
|
|
29
|
+
|
|
15
30
|
@dataclass
|
|
16
31
|
class RunContext:
|
|
17
32
|
"""一次运行的完整上下文"""
|
|
@@ -30,6 +45,10 @@ class RunContext:
|
|
|
30
45
|
|
|
31
46
|
# 分支(并行 Agent 用)
|
|
32
47
|
branch: Optional[str] = None
|
|
48
|
+
# 挂起记录(框架托管,业务侧无需关注内部协议)
|
|
49
|
+
suspensions: list[SuspensionRecord] = field(default_factory=list)
|
|
50
|
+
# resume 幂等记录:idempotency_key -> 已处理结果
|
|
51
|
+
resume_idempotency: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
33
52
|
|
|
34
53
|
def add_message(self, role: str, content: Any) -> None:
|
|
35
54
|
self.messages.append({"role": role, "content": content})
|
|
@@ -49,6 +68,45 @@ class RunContext:
|
|
|
49
68
|
branch_ctx.branch = branch_name
|
|
50
69
|
return branch_ctx
|
|
51
70
|
|
|
71
|
+
def register_suspension(
|
|
72
|
+
self,
|
|
73
|
+
*,
|
|
74
|
+
tool_call_id: str,
|
|
75
|
+
tool_name: str,
|
|
76
|
+
prompt: str,
|
|
77
|
+
form_schema: dict[str, Any] | None = None,
|
|
78
|
+
resume_strategy: str = "as_tool_result",
|
|
79
|
+
) -> SuspensionRecord:
|
|
80
|
+
record = SuspensionRecord(
|
|
81
|
+
suspension_id=str(uuid4()),
|
|
82
|
+
tool_call_id=tool_call_id,
|
|
83
|
+
tool_name=tool_name,
|
|
84
|
+
prompt=prompt,
|
|
85
|
+
form_schema=form_schema,
|
|
86
|
+
resume_strategy=resume_strategy,
|
|
87
|
+
)
|
|
88
|
+
self.suspensions.append(record)
|
|
89
|
+
return record
|
|
90
|
+
|
|
91
|
+
def get_pending_suspension(self, suspension_id: str | None = None) -> SuspensionRecord | None:
|
|
92
|
+
pending = [s for s in self.suspensions if s.resolved_at is None]
|
|
93
|
+
if not pending:
|
|
94
|
+
return None
|
|
95
|
+
if suspension_id:
|
|
96
|
+
for s in pending:
|
|
97
|
+
if s.suspension_id == suspension_id:
|
|
98
|
+
return s
|
|
99
|
+
return None
|
|
100
|
+
return pending[-1]
|
|
101
|
+
|
|
102
|
+
def resolve_suspension(self, suspension_id: str, user_input: str) -> SuspensionRecord | None:
|
|
103
|
+
for s in self.suspensions:
|
|
104
|
+
if s.suspension_id == suspension_id and s.resolved_at is None:
|
|
105
|
+
s.resolved_at = time.time()
|
|
106
|
+
s.resolved_input = user_input
|
|
107
|
+
return s
|
|
108
|
+
return None
|
|
109
|
+
|
|
52
110
|
def to_dict(self) -> dict[str, Any]:
|
|
53
111
|
"""将 RunContext 序列化为字典,支持 shared_context 自定义协议"""
|
|
54
112
|
serialized_shared = None
|
|
@@ -74,6 +132,8 @@ class RunContext:
|
|
|
74
132
|
"messages": copy.deepcopy(self.messages),
|
|
75
133
|
"state": copy.deepcopy(self.state),
|
|
76
134
|
"branch": self.branch,
|
|
135
|
+
"suspensions": [asdict(s) for s in self.suspensions],
|
|
136
|
+
"resume_idempotency": copy.deepcopy(self.resume_idempotency),
|
|
77
137
|
}
|
|
78
138
|
|
|
79
139
|
@classmethod
|
|
@@ -94,6 +154,8 @@ class RunContext:
|
|
|
94
154
|
messages=data.get("messages", []),
|
|
95
155
|
state=data.get("state", {}),
|
|
96
156
|
branch=data.get("branch"),
|
|
157
|
+
suspensions=[SuspensionRecord(**s) for s in data.get("suspensions", [])],
|
|
158
|
+
resume_idempotency=data.get("resume_idempotency", {}),
|
|
97
159
|
)
|
|
98
160
|
|
|
99
161
|
def to_json(self) -> str:
|
|
@@ -104,4 +166,3 @@ class RunContext:
|
|
|
104
166
|
def from_json(cls, json_str: str, shared_context_cls: Optional[Any] = None) -> "RunContext":
|
|
105
167
|
"""从 JSON 字符串反序列化 RunContext"""
|
|
106
168
|
return cls.from_dict(json.loads(json_str), shared_context_cls)
|
|
107
|
-
|