agstack 2.4.0__tar.gz → 3.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. {agstack-2.4.0 → agstack-3.0.0}/PKG-INFO +6 -9
  2. {agstack-2.4.0 → agstack-3.0.0}/README.md +5 -8
  3. agstack-3.0.0/agstack/genai/__init__.py +10 -0
  4. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/agent.py +61 -1
  5. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/detect_node.py +1 -1
  6. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/echo_node.py +3 -3
  7. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/llm_chat_node.py +1 -1
  8. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/llm_embed_node.py +1 -1
  9. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/llm_rerank_node.py +1 -1
  10. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/registry.py +1 -1
  11. agstack-3.0.0/agstack/genai/harness/__init__.py +120 -0
  12. agstack-3.0.0/agstack/genai/harness/context.py +411 -0
  13. agstack-3.0.0/agstack/genai/harness/overflow.py +130 -0
  14. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/harness/ports.py +2 -2
  15. {agstack-2.4.0/agstack → agstack-3.0.0/agstack/genai}/llm/client.py +3 -3
  16. {agstack-2.4.0/agstack → agstack-3.0.0/agstack/genai}/llm/hooks.py +2 -2
  17. {agstack-2.4.0 → agstack-3.0.0}/agstack.egg-info/PKG-INFO +6 -9
  18. agstack-3.0.0/agstack.egg-info/SOURCES.txt +102 -0
  19. {agstack-2.4.0 → agstack-3.0.0}/pyproject.toml +1 -1
  20. {agstack-2.4.0 → agstack-3.0.0}/tests/test_agent_guards.py +14 -14
  21. agstack-3.0.0/tests/test_agent_overflow.py +203 -0
  22. {agstack-2.4.0 → agstack-3.0.0}/tests/test_agent_parallel_tools.py +9 -9
  23. {agstack-2.4.0 → agstack-3.0.0}/tests/test_agent_request_overrides.py +6 -6
  24. {agstack-2.4.0 → agstack-3.0.0}/tests/test_flow_cancellation.py +9 -9
  25. {agstack-2.4.0 → agstack-3.0.0}/tests/test_flow_error_semantics.py +16 -16
  26. {agstack-2.4.0 → agstack-3.0.0}/tests/test_flow_io.py +30 -30
  27. {agstack-2.4.0 → agstack-3.0.0}/tests/test_flow_iterator.py +11 -11
  28. {agstack-2.4.0 → agstack-3.0.0}/tests/test_flow_switch_subflow.py +8 -8
  29. agstack-3.0.0/tests/test_harness_context.py +224 -0
  30. agstack-2.4.0/tests/test_harness_events_projection_tokens.py → agstack-3.0.0/tests/test_harness_events_projection_metering.py +5 -5
  31. {agstack-2.4.0 → agstack-3.0.0}/tests/test_harness_ports_truncation.py +4 -4
  32. {agstack-2.4.0 → agstack-3.0.0}/tests/test_harness_spill.py +3 -3
  33. {agstack-2.4.0 → agstack-3.0.0}/tests/test_llm_call_hooks.py +3 -3
  34. {agstack-2.4.0 → agstack-3.0.0}/tests/test_llm_usage_callback.py +2 -2
  35. {agstack-2.4.0 → agstack-3.0.0}/tests/test_tool_hooks.py +6 -6
  36. agstack-2.4.0/agstack/llm/harness/__init__.py +0 -71
  37. agstack-2.4.0/agstack.egg-info/SOURCES.txt +0 -97
  38. {agstack-2.4.0 → agstack-3.0.0}/LICENSE +0 -0
  39. {agstack-2.4.0 → agstack-3.0.0}/agstack/__init__.py +0 -0
  40. {agstack-2.4.0 → agstack-3.0.0}/agstack/cache/__init__.py +0 -0
  41. {agstack-2.4.0 → agstack-3.0.0}/agstack/cache/base.py +0 -0
  42. {agstack-2.4.0 → agstack-3.0.0}/agstack/cache/memory.py +0 -0
  43. {agstack-2.4.0 → agstack-3.0.0}/agstack/cache/redis.py +0 -0
  44. {agstack-2.4.0 → agstack-3.0.0}/agstack/config/__init__.py +0 -0
  45. {agstack-2.4.0 → agstack-3.0.0}/agstack/config/logger.py +0 -0
  46. {agstack-2.4.0 → agstack-3.0.0}/agstack/config/manager.py +0 -0
  47. {agstack-2.4.0 → agstack-3.0.0}/agstack/config/types.py +0 -0
  48. {agstack-2.4.0 → agstack-3.0.0}/agstack/contexts.py +0 -0
  49. {agstack-2.4.0 → agstack-3.0.0}/agstack/decorators.py +0 -0
  50. {agstack-2.4.0 → agstack-3.0.0}/agstack/events.py +0 -0
  51. {agstack-2.4.0 → agstack-3.0.0}/agstack/exceptions.py +0 -0
  52. {agstack-2.4.0 → agstack-3.0.0}/agstack/fastapi/__init__.py +0 -0
  53. {agstack-2.4.0 → agstack-3.0.0}/agstack/fastapi/exception.py +0 -0
  54. {agstack-2.4.0 → agstack-3.0.0}/agstack/fastapi/middleware.py +0 -0
  55. {agstack-2.4.0 → agstack-3.0.0}/agstack/fastapi/offline.py +0 -0
  56. {agstack-2.4.0 → agstack-3.0.0}/agstack/fastapi/sse.py +0 -0
  57. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/__init__.py +0 -0
  58. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/context.py +0 -0
  59. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/event.py +0 -0
  60. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/exceptions.py +0 -0
  61. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/factory.py +0 -0
  62. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/flow.py +0 -0
  63. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/guards.py +0 -0
  64. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/loader.py +0 -0
  65. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/__init__.py +0 -0
  66. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/agent_node.py +0 -0
  67. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/base.py +0 -0
  68. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/iterator_node.py +0 -0
  69. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/python_node.py +0 -0
  70. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/subflow_node.py +0 -0
  71. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/switch_node.py +0 -0
  72. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/nodes/tool_node.py +0 -0
  73. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/records.py +0 -0
  74. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/sandbox.py +0 -0
  75. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/state.py +0 -0
  76. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/tool.py +0 -0
  77. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/flow/trace.py +0 -0
  78. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/harness/events.py +0 -0
  79. /agstack-2.4.0/agstack/llm/harness/tokens.py → /agstack-3.0.0/agstack/genai/harness/metering.py +0 -0
  80. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/harness/projection.py +0 -0
  81. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/harness/spill.py +0 -0
  82. {agstack-2.4.0/agstack/llm → agstack-3.0.0/agstack/genai}/harness/truncation.py +0 -0
  83. {agstack-2.4.0/agstack → agstack-3.0.0/agstack/genai}/llm/__init__.py +0 -0
  84. {agstack-2.4.0/agstack → agstack-3.0.0/agstack/genai}/llm/prompts.py +0 -0
  85. {agstack-2.4.0/agstack → agstack-3.0.0/agstack/genai}/llm/token.py +0 -0
  86. {agstack-2.4.0 → agstack-3.0.0}/agstack/infra/db/__init__.py +0 -0
  87. {agstack-2.4.0 → agstack-3.0.0}/agstack/infra/es/__init__.py +0 -0
  88. {agstack-2.4.0 → agstack-3.0.0}/agstack/infra/kg/__init__.py +0 -0
  89. {agstack-2.4.0 → agstack-3.0.0}/agstack/infra/mq/__init__.py +0 -0
  90. {agstack-2.4.0 → agstack-3.0.0}/agstack/messagebus/__init__.py +0 -0
  91. {agstack-2.4.0 → agstack-3.0.0}/agstack/messagebus/base.py +0 -0
  92. {agstack-2.4.0 → agstack-3.0.0}/agstack/messagebus/memory.py +0 -0
  93. {agstack-2.4.0 → agstack-3.0.0}/agstack/messagebus/redis.py +0 -0
  94. {agstack-2.4.0 → agstack-3.0.0}/agstack/schema.py +0 -0
  95. {agstack-2.4.0 → agstack-3.0.0}/agstack/security/__init__.py +0 -0
  96. {agstack-2.4.0 → agstack-3.0.0}/agstack/security/casbin.py +0 -0
  97. {agstack-2.4.0 → agstack-3.0.0}/agstack/security/crypt.py +0 -0
  98. {agstack-2.4.0 → agstack-3.0.0}/agstack/status.py +0 -0
  99. {agstack-2.4.0 → agstack-3.0.0}/agstack.egg-info/dependency_links.txt +0 -0
  100. {agstack-2.4.0 → agstack-3.0.0}/agstack.egg-info/requires.txt +0 -0
  101. {agstack-2.4.0 → agstack-3.0.0}/agstack.egg-info/top_level.txt +0 -0
  102. {agstack-2.4.0 → agstack-3.0.0}/setup.cfg +0 -0
  103. {agstack-2.4.0 → agstack-3.0.0}/tests/test_cache_memory.py +0 -0
  104. {agstack-2.4.0 → agstack-3.0.0}/tests/test_cache_redis.py +0 -0
  105. {agstack-2.4.0 → agstack-3.0.0}/tests/test_messagebus_memory.py +0 -0
  106. {agstack-2.4.0 → agstack-3.0.0}/tests/test_messagebus_redis.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agstack
3
- Version: 2.4.0
3
+ Version: 3.0.0
4
4
  Summary: Production-ready toolkit for building FastAPI and LLM applications
5
5
  Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
6
6
  Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
@@ -86,7 +86,7 @@ pip install agstack
86
86
  ### Basic Usage
87
87
 
88
88
  ```python
89
- from agstack.llm.flow import (
89
+ from agstack.genai.flow import (
90
90
  Tool,
91
91
  FlowContext,
92
92
  registry,
@@ -130,13 +130,10 @@ agstack/
130
130
  ├── schema.py # Base Pydantic models
131
131
  ├── registry.py # Global component registry
132
132
  ├── exceptions.py # Exception hierarchy
133
- ├── llm/ # LLM and AI features
134
- │ ├── client.py # LLM client
135
- │ └── flow/ # Flow execution framework
136
- │ ├── agent.py # Agent definition
137
- │ ├── tool.py # Tool definition
138
- │ ├── flow.py # Flow orchestration
139
- │ └── ...
133
+ ├── genai/ # Generative AI, three one-way layers
134
+ │ ├── llm/ # Model access: client, call hooks, prompts, token counting
135
+ │ ├── flow/ # Flow execution framework (agent / tool / flow / nodes / guards)
136
+ │ └── harness/ # Runtime: ports, events, projection, metering, overflow, context, spill
140
137
  ├── fastapi/ # FastAPI integration
141
138
  ├── infra/ # Infrastructure components
142
139
  │ ├── db/ # Database
@@ -28,7 +28,7 @@ pip install agstack
28
28
  ### Basic Usage
29
29
 
30
30
  ```python
31
- from agstack.llm.flow import (
31
+ from agstack.genai.flow import (
32
32
  Tool,
33
33
  FlowContext,
34
34
  registry,
@@ -72,13 +72,10 @@ agstack/
72
72
  ├── schema.py # Base Pydantic models
73
73
  ├── registry.py # Global component registry
74
74
  ├── exceptions.py # Exception hierarchy
75
- ├── llm/ # LLM and AI features
76
- │ ├── client.py # LLM client
77
- │ └── flow/ # Flow execution framework
78
- │ ├── agent.py # Agent definition
79
- │ ├── tool.py # Tool definition
80
- │ ├── flow.py # Flow orchestration
81
- │ └── ...
75
+ ├── genai/ # Generative AI, three one-way layers
76
+ │ ├── llm/ # Model access: client, call hooks, prompts, token counting
77
+ │ ├── flow/ # Flow execution framework (agent / tool / flow / nodes / guards)
78
+ │ └── harness/ # Runtime: ports, events, projection, metering, overflow, context, spill
82
79
  ├── fastapi/ # FastAPI integration
83
80
  ├── infra/ # Infrastructure components
84
81
  │ ├── db/ # Database
@@ -0,0 +1,10 @@
1
+ # Copyright (c) 2020-2026 XtraVisions, All rights reserved.
2
+
3
+ """agstack.genai——生成式 AI 三层(3.0)
4
+
5
+ - :mod:`.llm`:模型接入(client / hooks / prompts / token),不依赖另外两层;
6
+ - :mod:`.flow`:编排(Agent / Tool / Flow / nodes / guards),依赖 llm;
7
+ - :mod:`.harness`:运行时纪律(ports / events / projection / metering / context / overflow / spill),依赖 llm 与 flow。
8
+
9
+ 依赖单向、不成环;应用对本包的依赖应集中在一处适配层。
10
+ """
@@ -7,7 +7,8 @@ import json
7
7
  from typing import TYPE_CHECKING, Any, AsyncIterator
8
8
  from uuid import uuid4
9
9
 
10
- from ..client import get_llm_client
10
+ from ..harness.overflow import OverflowPolicy, classify_overflow, usage_tokens
11
+ from ..llm.client import get_llm_client
11
12
  from . import event
12
13
  from .context import Usage
13
14
  from .event import EventType
@@ -22,6 +23,9 @@ if TYPE_CHECKING:
22
23
  class Agent:
23
24
  """Agent 定义"""
24
25
 
26
+ #: 上下文溢出恢复策略(harness.overflow;None 不恢复)。子类可按类属性给缺省,构造参数 ``overflow`` 覆盖
27
+ overflow: OverflowPolicy | None = None
28
+
25
29
  def __init__(
26
30
  self,
27
31
  name: str,
@@ -37,6 +41,7 @@ class Agent:
37
41
  retry_empty_response: bool = False,
38
42
  label: str | None = None,
39
43
  echo: bool = False,
44
+ overflow: OverflowPolicy | None = None,
40
45
  ):
41
46
  """初始化 Agent
42
47
 
@@ -52,6 +57,7 @@ class Agent:
52
57
  以 ``request_overrides(..., retry=True)`` 的覆盖参数重试一次
53
58
  :param label: 面向用户的展示名称(控制 STEP 进度事件可见性)
54
59
  :param echo: 是否转发 TEXT_MESSAGE 给用户
60
+ :param overflow: 上下文溢出恢复策略(三态判定命中后调 ``compact`` 压缩并同轮重发一次,见 harness.overflow)
55
61
  """
56
62
  self.name = name
57
63
  self.instructions = instructions or f"You are {name}, a helpful AI assistant."
@@ -65,6 +71,8 @@ class Agent:
65
71
  self.retry_empty_response = retry_empty_response
66
72
  self.label = label
67
73
  self.echo = echo
74
+ if overflow is not None:
75
+ self.overflow = overflow
68
76
 
69
77
  def get_system_message(self) -> dict[str, Any]:
70
78
  """获取系统消息"""
@@ -102,6 +110,25 @@ class Agent:
102
110
  return tool
103
111
  return None
104
112
 
113
+ # ── 溢出恢复(机制;策略在 self.overflow) ──
114
+
115
+ def _max_recoveries(self) -> int:
116
+ return self.overflow.max_recoveries if self.overflow is not None else 0
117
+
118
+ def _overflow_kind(self, context: "FlowContext", *, error: BaseException | None = None, **usage: Any) -> str | None:
119
+ if self.overflow is None:
120
+ return None
121
+ if error is not None:
122
+ return classify_overflow(error=error)
123
+ return classify_overflow(context_length=self.overflow.window(context), **usage)
124
+
125
+ async def _compact_overflow(self, context: "FlowContext", kind: str) -> bool:
126
+ assert self.overflow is not None
127
+ try:
128
+ return bool(await self.overflow.compact(context, kind))
129
+ except Exception: # noqa: BLE001 — 压缩失败不替代原错误,按原路报错
130
+ return False
131
+
105
132
  def _group_tool_calls(self, tool_calls: list[dict[str, Any]]) -> list[list[dict[str, Any]]]:
106
133
  """按声明分组:连续的 concurrency_safe 调用聚为一组并发执行,其余单独成组串行
107
134
 
@@ -290,6 +317,7 @@ class Agent:
290
317
 
291
318
  # Agent 循环
292
319
  assistant_content = ""
320
+ recoveries = 0 # 溢出恢复次数(每次运行上限 overflow.max_recoveries)
293
321
  for turn in range(1, self.max_turns + 1):
294
322
  # 协作式取消检查点:不再开始新的 LLM 轮次
295
323
  if context.is_cancelled:
@@ -306,6 +334,8 @@ class Agent:
306
334
  assistant_content = ""
307
335
  tool_calls: list[dict[str, Any]] = []
308
336
  tool_calls_buffer: dict[int, dict[str, Any]] = {}
337
+ finish_reason: str | None = None
338
+ turn_usage: Any = None
309
339
 
310
340
  try:
311
341
  kwargs: dict[str, Any] = {
@@ -362,6 +392,7 @@ class Agent:
362
392
 
363
393
  # 完成
364
394
  if choice.finish_reason:
395
+ finish_reason = choice.finish_reason
365
396
  # AG-UI: 工具调用事件
366
397
  for tool_call_data in tool_calls_buffer.values():
367
398
  tool_calls.append(tool_call_data)
@@ -383,6 +414,7 @@ class Agent:
383
414
 
384
415
  # 更新 usage
385
416
  if hasattr(chunk, "usage") and chunk.usage:
417
+ turn_usage = chunk.usage
386
418
  context.add_usage(
387
419
  Usage(
388
420
  prompt_tokens=chunk.usage.prompt_tokens or 0,
@@ -392,11 +424,39 @@ class Agent:
392
424
  )
393
425
 
394
426
  except Exception as e:
427
+ # 上下文溢出(后端报错态):尚未放出任何内容时压缩并同轮重发一次(harness.overflow)
428
+ kind = self._overflow_kind(context, error=e) if not assistant_content and not tool_calls else None
429
+ if kind and recoveries < self._max_recoveries():
430
+ recoveries += 1
431
+ yield event.custom(
432
+ name="agent_overflow", value={"agentName": self.name, "kind": kind, "turn": turn}
433
+ )
434
+ if await self._compact_overflow(context, kind):
435
+ messages = [self.get_system_message()] + context.history + context.get_messages(self.name)
436
+ continue
395
437
  error_msg = str(e)
396
438
  # AG-UI: RUN_ERROR
397
439
  yield event.run_error(message=error_msg)
398
440
  raise FlowError("AGENT_EXECUTION_FAILED", 500, {"error": error_msg}) from e
399
441
 
442
+ # 上下文溢出(静默 / length 态):请求成功但输入已撑爆窗口且本次没有产出,同样压缩重发一次
443
+ if not tool_calls and not assistant_content.strip():
444
+ prompt_tokens, completion_tokens = usage_tokens(turn_usage)
445
+ kind = self._overflow_kind(
446
+ context,
447
+ finish_reason=finish_reason,
448
+ prompt_tokens=prompt_tokens,
449
+ completion_tokens=completion_tokens,
450
+ )
451
+ if kind and recoveries < self._max_recoveries():
452
+ recoveries += 1
453
+ yield event.custom(
454
+ name="agent_overflow", value={"agentName": self.name, "kind": kind, "turn": turn}
455
+ )
456
+ if await self._compact_overflow(context, kind):
457
+ messages = [self.get_system_message()] + context.history + context.get_messages(self.name)
458
+ continue
459
+
400
460
  if self.retry_empty_response and attempt == 0 and not tool_calls and not assistant_content.strip():
401
461
  attempt = 1
402
462
  continue
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from openai.types.chat import ChatCompletionMessageParam
8
8
 
9
- from ...client import get_llm_client
9
+ from ...llm.client import get_llm_client
10
10
  from ..context import Usage
11
11
  from .base import NodeHandler
12
12
 
@@ -5,12 +5,12 @@
5
5
  from typing import TYPE_CHECKING, Any, AsyncIterator
6
6
  from uuid import uuid4
7
7
 
8
- from agstack.llm.flow import event
9
- from agstack.llm.flow.nodes.base import NodeHandler
8
+ from agstack.genai.flow import event
9
+ from agstack.genai.flow.nodes.base import NodeHandler
10
10
 
11
11
 
12
12
  if TYPE_CHECKING:
13
- from agstack.llm.flow.context import FlowContext
13
+ from agstack.genai.flow.context import FlowContext
14
14
 
15
15
 
16
16
  class EchoNodeHandler(NodeHandler):
@@ -8,7 +8,7 @@ from uuid import uuid4
8
8
 
9
9
  from openai.types.chat import ChatCompletionMessageParam
10
10
 
11
- from ...client import get_llm_client
11
+ from ...llm.client import get_llm_client
12
12
  from .. import event
13
13
  from ..context import Usage
14
14
  from .base import NodeHandler
@@ -4,7 +4,7 @@
4
4
 
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
- from ...client import get_llm_client
7
+ from ...llm.client import get_llm_client
8
8
  from .base import NodeHandler
9
9
 
10
10
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  from typing import TYPE_CHECKING, Any
6
6
 
7
- from ...client import get_llm_client
7
+ from ...llm.client import get_llm_client
8
8
  from .base import NodeHandler
9
9
 
10
10
 
@@ -7,7 +7,7 @@ from __future__ import annotations
7
7
  import copy
8
8
  from typing import Any, cast
9
9
 
10
- from ..hooks import LLMCallHook, clear_llm_hooks, register_llm_hook
10
+ from ..llm.hooks import LLMCallHook, clear_llm_hooks, register_llm_hook
11
11
  from .agent import Agent
12
12
  from .tool import Tool, ToolHook, clear_tool_hooks, register_tool_hook
13
13
 
@@ -0,0 +1,120 @@
1
+ # Copyright (c) 2020-2026 XtraVisions, All rights reserved.
2
+
3
+ """agstack.genai.harness——模型无关、表无关、产品无关的运行时部件
4
+
5
+ - :mod:`.ports`:存储端口声明(SessionLog / SpillStore / UsageSink / KVStore)与 ``register_ports``,
6
+ 由应用实现并在进程入口注册;
7
+ - :mod:`.truncation`:工具结果截断设施(保头尾截断、按相关度整条丢弃),策略数值由调用方给;
8
+ - :mod:`.spill`:超长工具结果落盘的 ToolHook(prepend 链头、按内联 token 上限判定、头尾保留 + 固定格式通知、
9
+ 存储失败保留内联);
10
+ - :mod:`.events`(2.4):任务事件枢纽 EventHub(序号 / 快照 / 订阅 / 重放,持久化由应用注入)与「flow 事件 → 用户可见
11
+ 事件」过滤规则表;
12
+ - :mod:`.projection`(2.4):「日志 → 模型历史」投影引擎(遮蔽过滤 / 事件投影 / 正文改写 / 注记 / 同角色合并,
13
+ 规则由应用给);
14
+ - :mod:`.metering`(2.4,原 tokens):估算校准(实报 / 估算钳制)与锚点增量计量;
15
+ - :mod:`.context`(3.0):对话上下文压缩引擎 ContextEngine(预算分层 / 快速路径 / 锚点计量 / 摘要消费与后台刷新 /
16
+ 两遍装填),历史来源与摘要存取经 HistorySource / SummaryStore 由应用实现;
17
+ - :mod:`.overflow`(3.0):上下文溢出三态判定与 ``OverflowPolicy``(``Agent.stream`` 命中后调应用的压缩协程并
18
+ 同轮重发一次)。
19
+
20
+ LLM 调用钩子 :mod:`agstack.genai.llm.hooks`(F6)留在 llm 包——它织入 LLMClient,client 不能反向依赖 harness——
21
+ 这里重导出。Agent 守卫机制(AgentGuards)在 :mod:`agstack.genai.flow.guards`。
22
+ 3.0 起 llm / flow / harness 收为 ``agstack.genai`` 三层,``agstack.genai.llm`` 只留模型接入(client / hooks / prompts /
23
+ token);2.x 的 ``agstack.llm`` 系路径不再保留(破坏性变更,随 major)。
24
+ """
25
+
26
+ from ..llm.hooks import CallMeta, LLMCallHook, StreamSummary, clear_llm_hooks, register_llm_hook
27
+ from .context import (
28
+ ContextEngine,
29
+ ContextPolicy,
30
+ HistorySource,
31
+ SummaryRecord,
32
+ SummaryStore,
33
+ anchored_history_tokens,
34
+ fill_to_budget,
35
+ messages_tokens,
36
+ schedule_once,
37
+ )
38
+ from .events import EventHub, TaskSnapshot, filter_user_event
39
+ from .metering import CalibratedCounter, anchored_estimate, calibration_from_samples, calibration_sample, clamp_ratio
40
+ from .overflow import (
41
+ OVERFLOW_ERROR,
42
+ OVERFLOW_LENGTH,
43
+ OVERFLOW_RECORD,
44
+ OVERFLOW_SILENT,
45
+ OverflowPolicy,
46
+ classify_overflow,
47
+ usage_tokens,
48
+ )
49
+ from .ports import (
50
+ KVStore,
51
+ LogEvent,
52
+ Ports,
53
+ SessionLog,
54
+ SpillOwner,
55
+ SpillRef,
56
+ SpillSource,
57
+ SpillStore,
58
+ TokenAnchor,
59
+ UsageSink,
60
+ clear_ports,
61
+ get_ports,
62
+ register_ports,
63
+ )
64
+ from .projection import Projection, is_shadowed, merge_consecutive, select_recent
65
+ from .spill import SpillHook, SpillPolicy
66
+ from .truncation import clamp_results, truncate_middle
67
+
68
+
69
+ __all__ = [
70
+ "ContextEngine",
71
+ "ContextPolicy",
72
+ "HistorySource",
73
+ "SummaryRecord",
74
+ "SummaryStore",
75
+ "anchored_history_tokens",
76
+ "fill_to_budget",
77
+ "messages_tokens",
78
+ "schedule_once",
79
+ "CallMeta",
80
+ "LLMCallHook",
81
+ "StreamSummary",
82
+ "clear_llm_hooks",
83
+ "register_llm_hook",
84
+ "OVERFLOW_ERROR",
85
+ "OVERFLOW_LENGTH",
86
+ "OVERFLOW_RECORD",
87
+ "OVERFLOW_SILENT",
88
+ "CalibratedCounter",
89
+ "EventHub",
90
+ "OverflowPolicy",
91
+ "Projection",
92
+ "classify_overflow",
93
+ "usage_tokens",
94
+ "TaskSnapshot",
95
+ "anchored_estimate",
96
+ "calibration_from_samples",
97
+ "calibration_sample",
98
+ "clamp_ratio",
99
+ "filter_user_event",
100
+ "is_shadowed",
101
+ "merge_consecutive",
102
+ "select_recent",
103
+ "KVStore",
104
+ "LogEvent",
105
+ "Ports",
106
+ "SessionLog",
107
+ "SpillHook",
108
+ "SpillOwner",
109
+ "SpillPolicy",
110
+ "SpillRef",
111
+ "SpillSource",
112
+ "SpillStore",
113
+ "TokenAnchor",
114
+ "UsageSink",
115
+ "clamp_results",
116
+ "clear_ports",
117
+ "get_ports",
118
+ "register_ports",
119
+ "truncate_middle",
120
+ ]