zhkj_llm_function 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,412 @@
1
+ """运行时:基于 tinyagent 执行 agent,产出框架统一返回结构。
2
+
3
+ 引擎:tinyagent-py(ReAct 循环 + 工具调用 + 上下文压缩 + token 统计)。
4
+ 框架层职责:
5
+ - @agent_def 声明的入参/出参校验
6
+ - 外部工具注册(参数 schema 由函数签名自动反射)
7
+ - 业务指令 docstring -> system prompt
8
+ - <|tool_call|> 标记协议兜底(tinyagent 原生工具调用之外的兼容路径)
9
+ - 会话 ttl 管控与统一返回结构封装
10
+
11
+ 框架返回结构(插件不用管):
12
+
13
+ {
14
+ "ok": bool,
15
+ "data": 输出模型实例,
16
+ "finish_reason": "stop|reset|compact|not_registered|input_schema_invalid|output_schema_invalid|tool_error|error",
17
+ "meta": {
18
+ "agent_name": str,
19
+ "token_original": int,
20
+ "token_compacted": int
21
+ }
22
+ }
23
+ """
24
+
25
+ import time
26
+ from datetime import datetime
27
+ from typing import Any, Dict, List, Optional
28
+
29
+ from .agent import AgentSpec
30
+ from .compat import messages_for_summary
31
+ from .errors import (
32
+ AgentNotRegisteredError,
33
+ FrameworkError,
34
+ InputSchemaError,
35
+ OutputSchemaError,
36
+ ToolExecutionError,
37
+ ToolNotFoundError,
38
+ )
39
+ from .protocol import extract_json_payload, parse_tool_call
40
+ from .schema import json_dumps, model_dump, model_validate
41
+ from .session import SessionStore
42
+ from .tools import ToolSpec, execute_tool, find_tool, normalize_tools, register_tools
43
+
44
+ FINISH_STOP = "stop"
45
+ FINISH_RESET = "reset"
46
+ FINISH_COMPACT = "compact"
47
+ FINISH_NOT_REGISTERED = "not_registered"
48
+ FINISH_INPUT_INVALID = "input_schema_invalid"
49
+ FINISH_OUTPUT_INVALID = "output_schema_invalid"
50
+ FINISH_TOOL_ERROR = "tool_error"
51
+ FINISH_ERROR = "error"
52
+
53
+ DEFAULT_COMPACT_CONFIG: Dict[str, Any] = {
54
+ "max_prompt_tokens": 8000,
55
+ "enabled": True,
56
+ }
57
+
58
+
59
+ class AgentRuntime(object):
60
+ """插件实例的运行时。"""
61
+
62
+ def __init__(
63
+ self,
64
+ group: Any,
65
+ model: str = "gpt-5",
66
+ api_key: Optional[str] = None,
67
+ model_kwargs: Optional[Dict[str, Any]] = None,
68
+ temperature: float = 0.0,
69
+ session_store: Optional[SessionStore] = None,
70
+ global_compact_config: Optional[Dict[str, Any]] = None,
71
+ max_tool_iterations: int = 8,
72
+ max_turns: int = 10,
73
+ enable_todo_write: bool = False,
74
+ storage: Any = None,
75
+ logger: Any = None,
76
+ ) -> None:
77
+ self.group = group
78
+ self.model = model
79
+ self.api_key = api_key
80
+ self.model_kwargs = dict(model_kwargs or {})
81
+ self.temperature = temperature
82
+ self.session_store = session_store or SessionStore()
83
+ self.global_compact_config = global_compact_config
84
+ self.max_tool_iterations = max_tool_iterations
85
+ self.max_turns = max_turns
86
+ self.enable_todo_write = enable_todo_write
87
+ self.storage = storage
88
+ self.logger = logger
89
+
90
+ # ------------------------------------------------------------------ 公共入口
91
+ async def run(
92
+ self,
93
+ agent_name: str,
94
+ payload: Any,
95
+ tools: Optional[List[Any]] = None,
96
+ session_id: Optional[str] = None,
97
+ ) -> Dict[str, Any]:
98
+ """执行一次 agent 调用,返回框架统一封装结构。"""
99
+ meta = {
100
+ "agent_name": agent_name,
101
+ "token_original": 0,
102
+ "token_compacted": 0,
103
+ "session_id": session_id or "",
104
+ "session_reset": False,
105
+ "iterations": 0,
106
+ "tool_calls": 0,
107
+ "compactions": 0,
108
+ }
109
+
110
+ try:
111
+ spec = self._resolve_spec(agent_name)
112
+ except AgentNotRegisteredError as exc:
113
+ return self._envelope(False, None, FINISH_NOT_REGISTERED, meta, str(exc))
114
+
115
+ meta["agent_name"] = spec.name
116
+
117
+ try:
118
+ tool_specs = normalize_tools(tools)
119
+ except FrameworkError as exc:
120
+ return self._envelope(False, None, FINISH_TOOL_ERROR, meta, str(exc))
121
+
122
+ try:
123
+ payload_data = self._validate_payload(spec, payload)
124
+ except InputSchemaError as exc:
125
+ return self._envelope(False, None, FINISH_INPUT_INVALID, meta, str(exc))
126
+
127
+ session, session_reset = self._resolve_session(spec, session_id)
128
+ meta["session_id"] = session.session_id
129
+ meta["session_reset"] = session_reset
130
+
131
+ agent = session.agent
132
+ if agent is None:
133
+ agent = self._create_agent(spec)
134
+ session.agent = agent
135
+ try:
136
+ register_tools(agent, tool_specs)
137
+ except Exception as exc: # noqa: BLE001
138
+ return self._envelope(False, None, FINISH_TOOL_ERROR, meta, str(exc))
139
+
140
+ user_input = self._build_user_input(payload_data)
141
+ final_text = ""
142
+
143
+ try:
144
+ for iteration in range(self.max_tool_iterations):
145
+ meta["iterations"] = iteration + 1
146
+
147
+ if iteration == 0:
148
+ final_text = await agent.run(user_input, max_turns=self.max_turns)
149
+ else:
150
+ final_text = await agent.resume(max_turns=self.max_turns)
151
+
152
+ # <|tool_call|> 标记协议兜底:兼容不走原生工具调用的模型
153
+ marker_call = parse_tool_call(final_text)
154
+ if marker_call is None:
155
+ break
156
+
157
+ meta["tool_calls"] += 1
158
+ try:
159
+ tool = find_tool(tool_specs, marker_call.name)
160
+ tool_result = await execute_tool(tool, marker_call.arguments)
161
+ except (ToolNotFoundError, ToolExecutionError) as exc:
162
+ return self._envelope(False, None, FINISH_TOOL_ERROR, meta, str(exc))
163
+
164
+ agent.messages.append(
165
+ {
166
+ "role": "user",
167
+ "content": "工具 {0} 执行结果:{1}".format(marker_call.name, tool_result),
168
+ "created_at": int(time.time()),
169
+ }
170
+ )
171
+ else:
172
+ return self._envelope(
173
+ False,
174
+ None,
175
+ FINISH_COMPACT,
176
+ meta,
177
+ "工具调用轮次超过上限 {0}".format(self.max_tool_iterations),
178
+ )
179
+ except FrameworkError as exc:
180
+ return self._envelope(False, None, FINISH_ERROR, meta, str(exc))
181
+ except Exception as exc: # noqa: BLE001
182
+ return self._envelope(
183
+ False, None, FINISH_ERROR, meta, "{0}: {1}".format(type(exc).__name__, exc)
184
+ )
185
+
186
+ # ---- token 统计与上下文压缩 ----
187
+ native_tool_calls = len(
188
+ [m for m in getattr(agent, "messages", []) or [] if m.get("role") == "tool"]
189
+ )
190
+ meta["tool_calls"] = meta["tool_calls"] + native_tool_calls
191
+ token_original = self._count_tokens(agent)
192
+ meta["token_original"] = token_original
193
+ meta["token_compacted"] = token_original
194
+
195
+ compact_config = self._compact_config(spec)
196
+ if compact_config.get("enabled", True) and token_original > int(
197
+ compact_config.get("max_prompt_tokens", DEFAULT_COMPACT_CONFIG["max_prompt_tokens"])
198
+ ):
199
+ try:
200
+ compacted = await self._compact_agent(agent)
201
+ if compacted is False:
202
+ meta["compact_error"] = "压缩未生效(引擎返回 False)"
203
+ else:
204
+ meta["compactions"] = 1
205
+ meta["token_compacted"] = self._count_tokens(agent)
206
+ except Exception as exc: # noqa: BLE001 - 压缩失败不阻塞主流程,但需可观测
207
+ meta["compact_error"] = "{0}: {1}".format(type(exc).__name__, exc)
208
+
209
+ # ---- 最终输出校验 ----
210
+ payload_obj = extract_json_payload(final_text)
211
+ if payload_obj is None:
212
+ return self._envelope(
213
+ False,
214
+ None,
215
+ FINISH_OUTPUT_INVALID,
216
+ meta,
217
+ "模型输出无法解析为 JSON: {0}".format((final_text or "")[:200]),
218
+ )
219
+
220
+ if isinstance(payload_obj, list):
221
+ payload_obj = {"items": payload_obj}
222
+
223
+ try:
224
+ output = model_validate(spec.output_model, payload_obj)
225
+ except Exception as exc: # noqa: BLE001 - pydantic 校验失败
226
+ return self._envelope(
227
+ False,
228
+ None,
229
+ FINISH_OUTPUT_INVALID,
230
+ meta,
231
+ "模型输出不符合 {0}: {1}".format(spec.output_model.__name__, exc),
232
+ )
233
+
234
+ finish_reason = FINISH_RESET if session_reset else FINISH_STOP
235
+ return self._envelope(True, output, finish_reason, meta, "")
236
+
237
+ # ------------------------------------------------------------------ 内部方法
238
+ def _resolve_spec(self, agent_name: str) -> AgentSpec:
239
+ """解析 agent 声明。"""
240
+ getter = getattr(self.group, "get_agent_spec", None)
241
+ if getter is None:
242
+ raise AgentNotRegisteredError("当前对象不是 BaseAgentGroup")
243
+ return getter(agent_name)
244
+
245
+ def _validate_payload(self, spec: AgentSpec, payload: Any) -> Dict[str, Any]:
246
+ """校验入参并转为 dict。"""
247
+ if payload is None:
248
+ raise InputSchemaError("payload 不能为空")
249
+ if isinstance(payload, spec.input_model):
250
+ return model_dump(payload)
251
+ if isinstance(payload, dict):
252
+ try:
253
+ return model_dump(model_validate(spec.input_model, payload))
254
+ except Exception as exc: # noqa: BLE001
255
+ raise InputSchemaError(
256
+ "入参不符合 {0}: {1}".format(spec.input_model.__name__, exc)
257
+ )
258
+ raise InputSchemaError(
259
+ "payload 必须是 {0} 实例或 dict,当前为 {1}".format(
260
+ spec.input_model.__name__, type(payload).__name__
261
+ )
262
+ )
263
+
264
+ def _compact_config(self, spec: AgentSpec) -> Dict[str, Any]:
265
+ """合并压缩配置:agent 声明优先,其次全局配置,最后默认值。"""
266
+ config = dict(DEFAULT_COMPACT_CONFIG)
267
+ if self.global_compact_config:
268
+ config.update({k: v for k, v in self.global_compact_config.items() if v is not None})
269
+ if spec.compact_config:
270
+ config.update({k: v for k, v in spec.compact_config.items() if v is not None})
271
+ return config
272
+
273
+ def _resolve_session(self, spec: AgentSpec, session_id: Optional[str]):
274
+ """获取或创建会话(会话内复用同一个 tinyagent 实例,ttl 到期即重置)。"""
275
+ if not session_id:
276
+ session = self.session_store.create(
277
+ spec.name, spec.ttl, agent=self._create_agent(spec)
278
+ )
279
+ return session, False
280
+
281
+ existing = self.session_store.peek(session_id)
282
+ if existing is None:
283
+ # 全新会话
284
+ session = self.session_store.create(
285
+ spec.name, spec.ttl, session_id=session_id, agent=self._create_agent(spec)
286
+ )
287
+ return session, False
288
+
289
+ if existing.expired:
290
+ # 会话过期 -> 重置
291
+ session = self.session_store.create(
292
+ spec.name, spec.ttl, session_id=session_id, agent=self._create_agent(spec)
293
+ )
294
+ return session, True
295
+
296
+ session = existing
297
+ if session.agent_name != spec.name:
298
+ session = self.session_store.reset(
299
+ session_id, spec.name, spec.ttl, agent=self._create_agent(spec)
300
+ )
301
+ return session, True
302
+
303
+ session.touch(spec.ttl)
304
+ return session, False
305
+
306
+ async def _compact_agent(self, agent: Any) -> bool:
307
+ """执行压缩。
308
+
309
+ 引擎在生成摘要时用 dict 方式读取 tool_calls,而运行期消息里是对象,
310
+ 这里临时换成可读形式;压缩失败时恢复原消息。
311
+ """
312
+ original = getattr(agent, "messages", []) or []
313
+ try:
314
+ agent.messages = messages_for_summary(list(original))
315
+ except Exception: # pragma: no cover - 转换失败时按原样压缩
316
+ agent.messages = original
317
+
318
+ try:
319
+ return await agent.compact()
320
+ except Exception:
321
+ if getattr(agent, "messages", None) is not original:
322
+ agent.messages = original
323
+ raise
324
+
325
+ def _create_agent(self, spec: AgentSpec) -> Any:
326
+ """按 agent 声明创建 tinyagent 实例。"""
327
+ from tinyagent import TinyAgent
328
+
329
+ return TinyAgent(
330
+ model=self.model,
331
+ api_key=self.api_key,
332
+ system_prompt=self._build_system_prompt(spec),
333
+ temperature=self.temperature,
334
+ model_kwargs=dict(self.model_kwargs),
335
+ enable_todo_write=self.enable_todo_write,
336
+ parallel_tool_calls=False,
337
+ enable_custom_instruction=False,
338
+ storage=self.storage,
339
+ logger=self.logger,
340
+ )
341
+
342
+ def _build_system_prompt(self, spec: AgentSpec) -> str:
343
+ """组装 system prompt:业务指令 + 输出约束 + 工具调用协议。"""
344
+ instruction = spec.business_instruction or (
345
+ "你是 {0} 助手,请根据用户输入完成任务。".format(spec.name)
346
+ )
347
+ blocks = [
348
+ instruction,
349
+ "\n【输出要求】\n最终仅输出符合以下 JSON Schema 的 JSON 对象,"
350
+ "不要附带任何解释、说明或 Markdown 标记:\n" + json_dumps(spec.output_schema),
351
+ "\n【工具调用】\n需要外部信息时必须调用工具,禁止凭空编造;"
352
+ "一次只能调用一个工具。可使用原生函数调用,或使用以下标记协议:\n"
353
+ "<|tool_call|>\n"
354
+ '{"name": "工具名称", "arguments": {}}\n'
355
+ "<|end_tool_call|>",
356
+ ]
357
+ return "\n".join(blocks)
358
+
359
+ def _build_user_input(self, payload_data: Dict[str, Any]) -> str:
360
+ """把入参转成用户消息(含当前日期,便于模型推导“明天”等相对日期)。"""
361
+ now = datetime.now()
362
+ return (
363
+ "当前日期时间:{0}({1})\n"
364
+ "如需相对日期(如今天/明天/后天),以此为基准换算。\n"
365
+ "用户输入:\n{2}"
366
+ ).format(
367
+ now.strftime("%Y-%m-%d %H:%M:%S"),
368
+ now.strftime("%A"),
369
+ json_dumps(payload_data),
370
+ )
371
+
372
+ @staticmethod
373
+ def _count_tokens(agent: Any) -> int:
374
+ """统计当前会话的 token 数。"""
375
+ try:
376
+ total = 0
377
+ for message in getattr(agent, "messages", []) or []:
378
+ content = message.get("content") or ""
379
+ total += agent.count_tokens(content)
380
+ return total
381
+ except Exception: # pragma: no cover
382
+ return 0
383
+
384
+ @staticmethod
385
+ def _envelope(
386
+ ok: bool,
387
+ data: Any,
388
+ finish_reason: str,
389
+ meta: Dict[str, Any],
390
+ error: str,
391
+ ) -> Dict[str, Any]:
392
+ """构造框架统一返回结构。"""
393
+ envelope: Dict[str, Any] = {
394
+ "ok": ok,
395
+ "data": data,
396
+ "finish_reason": finish_reason,
397
+ "meta": {
398
+ "agent_name": meta.get("agent_name", ""),
399
+ "token_original": meta.get("token_original", 0),
400
+ "token_compacted": meta.get("token_compacted", 0),
401
+ "session_id": meta.get("session_id", ""),
402
+ "session_reset": meta.get("session_reset", False),
403
+ "iterations": meta.get("iterations", 0),
404
+ "tool_calls": meta.get("tool_calls", 0),
405
+ "compactions": meta.get("compactions", 0),
406
+ },
407
+ }
408
+ if meta.get("compact_error"):
409
+ envelope["meta"]["compact_error"] = meta["compact_error"]
410
+ if error:
411
+ envelope["error"] = error
412
+ return envelope
@@ -0,0 +1,139 @@
1
+ """Pydantic v1/v2 兼容层与 JSON Schema 反射工具。
2
+
3
+ 框架不手写工具 parameters:工具函数的签名由 `inspect` + `typing.get_type_hints`
4
+ 反射生成 JSON Schema,agent 的入参/出参由 Pydantic 模型反射生成。
5
+ """
6
+
7
+ import inspect
8
+ import json
9
+ import typing
10
+ from typing import Any, Callable, Dict, List, Optional
11
+
12
+ from pydantic import BaseModel
13
+
14
+ _PYDANTIC_V2 = hasattr(BaseModel, "model_validate")
15
+
16
+
17
+ def model_validate(model_cls: type, data: Any) -> BaseModel:
18
+ """按当前 pydantic 版本校验数据。"""
19
+ if _PYDANTIC_V2:
20
+ return model_cls.model_validate(data)
21
+ return model_cls.parse_obj(data) # pragma: no cover - pydantic v1 分支
22
+
23
+
24
+ def model_dump(model: BaseModel) -> Dict[str, Any]:
25
+ """按当前 pydantic 版本导出字典。"""
26
+ if _PYDANTIC_V2:
27
+ return model.model_dump()
28
+ return model.dict() # pragma: no cover - pydantic v1 分支
29
+
30
+
31
+ def model_json(model: BaseModel) -> str:
32
+ """序列化为 JSON 字符串。"""
33
+ if _PYDANTIC_V2:
34
+ return model.model_dump_json()
35
+ return model.json() # pragma: no cover - pydantic v1 分支
36
+
37
+
38
+ def model_schema(model_cls: type) -> Dict[str, Any]:
39
+ """返回模型的 JSON Schema。"""
40
+ if _PYDANTIC_V2:
41
+ return model_cls.model_json_schema()
42
+ return model_cls.schema() # pragma: no cover - pydantic v1 分支
43
+
44
+
45
+ _JSON_TYPES = {
46
+ str: "string",
47
+ int: "integer",
48
+ float: "number",
49
+ bool: "boolean",
50
+ list: "array",
51
+ tuple: "array",
52
+ set: "array",
53
+ dict: "object",
54
+ type(None): "null",
55
+ }
56
+
57
+
58
+ def _type_to_schema(annotation: Any) -> Dict[str, Any]:
59
+ """把 Python 类型注解转换成 JSON Schema 片段。"""
60
+ if annotation is inspect.Parameter.empty or annotation is Any:
61
+ return {"type": "string"}
62
+
63
+ origin = typing.get_origin(annotation)
64
+ args = typing.get_args(annotation)
65
+
66
+ if origin is typing.Union:
67
+ non_none = [a for a in args if a is not type(None)] # noqa: E721
68
+ if len(non_none) == 1:
69
+ return _type_to_schema(non_none[0])
70
+ return {"anyOf": [_type_to_schema(a) for a in non_none]}
71
+
72
+ if origin in (list, set, tuple):
73
+ item = _type_to_schema(args[0]) if args else {"type": "string"}
74
+ return {"type": "array", "items": item}
75
+
76
+ if origin is dict:
77
+ return {"type": "object"}
78
+
79
+ if isinstance(annotation, type) and issubclass(annotation, BaseModel):
80
+ return model_schema(annotation)
81
+
82
+ if annotation in _JSON_TYPES:
83
+ return {"type": _JSON_TYPES[annotation]}
84
+
85
+ return {"type": "string"}
86
+
87
+
88
+ def tool_parameters_schema(func: Callable[..., Any]) -> Dict[str, Any]:
89
+ """从工具函数签名反射生成 JSON Schema(框架自动完成,无需手写)。"""
90
+ signature = inspect.signature(func)
91
+ try:
92
+ hints = typing.get_type_hints(func)
93
+ except Exception: # pragma: no cover - 注解无法解析时退化为空类型
94
+ hints = {}
95
+
96
+ properties: Dict[str, Any] = {}
97
+ required: List[str] = []
98
+
99
+ for name, param in signature.parameters.items():
100
+ if name in ("self", "cls"):
101
+ continue
102
+ if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
103
+ continue
104
+
105
+ annotation = hints.get(name, param.annotation)
106
+ properties[name] = _type_to_schema(annotation)
107
+ if param.default is inspect.Parameter.empty:
108
+ required.append(name)
109
+
110
+ return {"type": "object", "properties": properties, "required": required}
111
+
112
+
113
+ def describe_docstring(func: Callable[..., Any]) -> str:
114
+ """取函数的业务指令 docstring。"""
115
+ doc = inspect.getdoc(func)
116
+ return (doc or "").strip()
117
+
118
+
119
+ def json_dumps(data: Any) -> str:
120
+ """稳定的 JSON 序列化。"""
121
+ return json.dumps(data, ensure_ascii=False, default=str)
122
+
123
+
124
+ def resolve_model_hints(func: Callable[..., Any]) -> Dict[str, Any]:
125
+ """解析函数注解,失败时返回空字典。"""
126
+ try:
127
+ return typing.get_type_hints(func, include_extras=True)
128
+ except TypeError: # pragma: no cover - Python 3.8 无 include_extras
129
+ try:
130
+ return typing.get_type_hints(func)
131
+ except Exception:
132
+ return {}
133
+ except Exception: # pragma: no cover
134
+ return {}
135
+
136
+
137
+ def optional_str(value: Optional[str]) -> str:
138
+ """None 安全地转成字符串。"""
139
+ return "" if value is None else value
@@ -0,0 +1,95 @@
1
+ """会话存储:按 agent 的 ttl 管理会话有效期,会话内复用同一个引擎实例。
2
+
3
+ 会话状态只存在于内存中:进程重启即失效。
4
+ 需要跨重启保留历史时,请由业务侧自行保存/恢复(如把 session_id 映射到外部存储)。
5
+ """
6
+
7
+ import time
8
+ import uuid
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ class Session(object):
13
+ """一次会话的完整状态。"""
14
+
15
+ __slots__ = ("session_id", "agent_name", "messages", "created_at", "expires_at", "meta", "agent")
16
+
17
+ def __init__(
18
+ self,
19
+ session_id: str,
20
+ agent_name: str,
21
+ messages: List[Dict[str, Any]],
22
+ ttl: int,
23
+ meta: Optional[Dict[str, Any]] = None,
24
+ agent: Any = None,
25
+ ) -> None:
26
+ self.session_id = session_id
27
+ self.agent_name = agent_name
28
+ self.messages = messages
29
+ self.created_at = time.time()
30
+ self.expires_at = self.created_at + ttl
31
+ self.meta: Dict[str, Any] = meta or {}
32
+ self.agent = agent
33
+
34
+ @property
35
+ def expired(self) -> bool:
36
+ """会话是否已过期。"""
37
+ return time.time() >= self.expires_at
38
+
39
+ def touch(self, ttl: int) -> None:
40
+ """续期。"""
41
+ self.expires_at = time.time() + ttl
42
+
43
+
44
+ class SessionStore(object):
45
+ """内存会话存储。"""
46
+
47
+ def __init__(self) -> None:
48
+ self._sessions: Dict[str, Session] = {}
49
+
50
+ def get(self, session_id: str) -> Optional[Session]:
51
+ """获取未过期会话;已过期返回 None(保留记录以便识别“需重置”)。"""
52
+ session = self._sessions.get(session_id)
53
+ if session is None or session.expired:
54
+ return None
55
+ return session
56
+
57
+ def peek(self, session_id: str) -> Optional[Session]:
58
+ """获取会话(不过期检查),用于识别“已过期需重置”的会话。"""
59
+ return self._sessions.get(session_id)
60
+
61
+ def create(
62
+ self,
63
+ agent_name: str,
64
+ ttl: int,
65
+ session_id: Optional[str] = None,
66
+ messages: Optional[List[Dict[str, Any]]] = None,
67
+ agent: Any = None,
68
+ ) -> Session:
69
+ """新建会话。"""
70
+ sid = session_id or uuid.uuid4().hex
71
+ session = Session(sid, agent_name, list(messages or []), ttl, agent=agent)
72
+ self._sessions[sid] = session
73
+ return session
74
+
75
+ def list_session_ids(self) -> List[str]:
76
+ """当前进程内已知的 session_id 列表。"""
77
+ return sorted(self._sessions.keys())
78
+
79
+ def reset(
80
+ self,
81
+ session_id: str,
82
+ agent_name: str,
83
+ ttl: int,
84
+ agent: Any = None,
85
+ ) -> Session:
86
+ """重置(清空消息并重建引擎实例)会话。"""
87
+ return self.create(agent_name, ttl, session_id=session_id, agent=agent)
88
+
89
+ def drop(self, session_id: str) -> None:
90
+ """删除会话。"""
91
+ self._sessions.pop(session_id, None)
92
+
93
+ def clear(self) -> None:
94
+ """清空全部会话。"""
95
+ self._sessions.clear()