specmodule 0.3.0__tar.gz → 0.5.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.
- {specmodule-0.3.0/specmodule.egg-info → specmodule-0.5.0}/PKG-INFO +2 -2
- {specmodule-0.3.0 → specmodule-0.5.0}/llm/client.py +144 -14
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/__init__.py +4 -1
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/cli/cli.py +1421 -1433
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/cli/entry.py +39 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/cli/loader.py +222 -215
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/core/call.py +8 -2
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/core/config.py +19 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/core/harness.py +110 -48
- specmodule-0.5.0/module_harness/infra/artifacts.py +93 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/infra/checkpoint.py +36 -9
- specmodule-0.5.0/module_harness/infra/entry_pack.py +218 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/infra/events.py +6 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/infra/query.py +1001 -855
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/infra/store.py +87 -5
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/infra/stream.py +3 -1
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/model/module.py +48 -9
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/model/spec.py +56 -3
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/model/submodule.py +285 -280
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/model/translator.py +26 -2
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/orchestrate/graph_builder.py +7 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/pyproject.toml +4 -4
- {specmodule-0.3.0 → specmodule-0.5.0/specmodule.egg-info}/PKG-INFO +2 -2
- {specmodule-0.3.0 → specmodule-0.5.0}/specmodule.egg-info/SOURCES.txt +2 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/specmodule.egg-info/requires.txt +1 -1
- {specmodule-0.3.0 → specmodule-0.5.0}/LICENSE +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/README.md +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/llm/__init__.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/llm/config.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/llm/mock.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/cli/__init__.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/cli/__main__.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/cli/command.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/cli/scaffold.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/core/__init__.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/core/builtins.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/core/outputfmt.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/core/prompt.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/core/registry.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/infra/__init__.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/infra/control.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/infra/status.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/model/__init__.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/orchestrate/__init__.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/orchestrate/align.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/orchestrate/consistency.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/orchestrate/feed.py +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/templates/builtin/codereview.json +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/templates/builtin/docwrite.json +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/templates/builtin/summarize.json +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/module_harness/templates/builtin/translate.json +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/setup.cfg +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/specmodule.egg-info/dependency_links.txt +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/specmodule.egg-info/entry_points.txt +0 -0
- {specmodule-0.3.0 → specmodule-0.5.0}/specmodule.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specmodule
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: 可审计、可调试、可完全掌控的 LLM 使用框架(tickflow + llm + module_harness)
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: llm,workflow,petri-net,agent
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: tickflow-py<0.
|
|
18
|
+
Requires-Dist: tickflow-py<0.4,>=0.3.0
|
|
19
19
|
Provides-Extra: anthropic
|
|
20
20
|
Requires-Dist: anthropic; extra == "anthropic"
|
|
21
21
|
Provides-Extra: openai
|
|
@@ -136,6 +136,76 @@ def _safe_on_token(on_token: Callable[[str], None] | None, chunk: str) -> None:
|
|
|
136
136
|
log.exception("on_token 回调异常;已忽略")
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def _safe_on_thinking(on_thinking: Callable[[str], None] | None, chunk: str) -> None:
|
|
140
|
+
"""调用 on_thinking,回调异常不得影响主流程(同 _safe_on_token)。"""
|
|
141
|
+
if on_thinking is None or not chunk:
|
|
142
|
+
return
|
|
143
|
+
try:
|
|
144
|
+
on_thinking(chunk)
|
|
145
|
+
except Exception:
|
|
146
|
+
log.exception("on_thinking 回调异常;已忽略")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class _ThinkTagStripper:
|
|
150
|
+
"""跨 chunk 安全的内联 ``<think>…</think>`` 剥离器。
|
|
151
|
+
|
|
152
|
+
部分兼容网关不单设 reasoning 通道,思考文本带标签内联在 content 里。
|
|
153
|
+
feed() 逐 chunk 喂入,返回 (content_delta, thinking_delta):标签外增量
|
|
154
|
+
归 content、标签内增量归 thinking;hold-back 缓冲处理标签自身被 chunk
|
|
155
|
+
劈开的情况;flush() 在流结束吐出残留(未闭合标签按思考处理)。
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
_OPEN = "<think>"
|
|
159
|
+
_CLOSE = "</think>"
|
|
160
|
+
|
|
161
|
+
def __init__(self) -> None:
|
|
162
|
+
self._inside = False
|
|
163
|
+
self._buf = "" # hold-back:可能是未判定标签前缀的尾部
|
|
164
|
+
|
|
165
|
+
def feed(self, chunk: str) -> tuple[str, str]:
|
|
166
|
+
self._buf += chunk
|
|
167
|
+
content_parts: list[str] = []
|
|
168
|
+
thinking_parts: list[str] = []
|
|
169
|
+
while self._buf:
|
|
170
|
+
if self._inside:
|
|
171
|
+
end = self._buf.find(self._CLOSE)
|
|
172
|
+
if end >= 0:
|
|
173
|
+
thinking_parts.append(self._buf[:end])
|
|
174
|
+
self._buf = self._buf[end + len(self._CLOSE):]
|
|
175
|
+
self._inside = False
|
|
176
|
+
continue
|
|
177
|
+
keep = self._holdback(self._CLOSE)
|
|
178
|
+
else:
|
|
179
|
+
start = self._buf.find(self._OPEN)
|
|
180
|
+
if start >= 0:
|
|
181
|
+
content_parts.append(self._buf[:start])
|
|
182
|
+
self._buf = self._buf[start + len(self._OPEN):]
|
|
183
|
+
self._inside = True
|
|
184
|
+
continue
|
|
185
|
+
keep = self._holdback(self._OPEN)
|
|
186
|
+
emit = len(self._buf) - keep
|
|
187
|
+
if emit:
|
|
188
|
+
(thinking_parts if self._inside else content_parts).append(self._buf[:emit])
|
|
189
|
+
self._buf = self._buf[emit:]
|
|
190
|
+
break
|
|
191
|
+
return "".join(content_parts), "".join(thinking_parts)
|
|
192
|
+
|
|
193
|
+
def flush(self) -> tuple[str, str]:
|
|
194
|
+
"""流结束:按当前内/外状态吐出残留 hold-back。"""
|
|
195
|
+
if not self._buf:
|
|
196
|
+
return "", ""
|
|
197
|
+
out = self._buf
|
|
198
|
+
self._buf = ""
|
|
199
|
+
return ("", out) if self._inside else (out, "")
|
|
200
|
+
|
|
201
|
+
def _holdback(self, tag: str) -> int:
|
|
202
|
+
"""缓冲尾部可能是 tag 真前缀的最大长度( TagLen-1 向下探)。"""
|
|
203
|
+
for n in range(min(len(self._buf), len(tag) - 1), 0, -1):
|
|
204
|
+
if tag.startswith(self._buf[-n:]):
|
|
205
|
+
return n
|
|
206
|
+
return 0
|
|
207
|
+
|
|
208
|
+
|
|
139
209
|
# ---------------------------------------------------------------------------
|
|
140
210
|
# Anthropic 客户端
|
|
141
211
|
# ---------------------------------------------------------------------------
|
|
@@ -235,6 +305,7 @@ class AnthropicClient:
|
|
|
235
305
|
output_format: dict[str, Any] | None = None,
|
|
236
306
|
notdo: list[str] | None = None,
|
|
237
307
|
on_token: Callable[[str], None] | None = None,
|
|
308
|
+
on_thinking: Callable[[str], None] | None = None,
|
|
238
309
|
api_params: dict[str, Any] | None = None,
|
|
239
310
|
) -> LLMResponse:
|
|
240
311
|
"""单轮调用入口(harness body 用)。
|
|
@@ -242,7 +313,8 @@ class AnthropicClient:
|
|
|
242
313
|
- ``prompt``:三层渲染后的用户提示词
|
|
243
314
|
- ``model``/``temperature``/``think``:按调用覆盖,缺省回落 config
|
|
244
315
|
- ``output_format``:原生结构化输出(强制 tool-use)
|
|
245
|
-
- ``on_token``:流式 token
|
|
316
|
+
- ``on_token``/``on_thinking``:流式 token/思考增量回调(任一提供即走流式接口;
|
|
317
|
+
thinking_delta → on_thinking,text_delta → on_token;回调异常不破主流程)
|
|
246
318
|
- ``api_params``:透传给 SDK 的额外参数(已知字段入 kwargs,未知入 extra_body)
|
|
247
319
|
"""
|
|
248
320
|
self._require_ready()
|
|
@@ -282,8 +354,8 @@ class AnthropicClient:
|
|
|
282
354
|
_apply_api_params(kwargs, api_params, _KNOWN_ANTHROPIC_PARAMS)
|
|
283
355
|
|
|
284
356
|
try:
|
|
285
|
-
if on_token:
|
|
286
|
-
content, tool_calls, usage, finish = await self._stream(kwargs, forced_tool, on_token)
|
|
357
|
+
if on_token or on_thinking:
|
|
358
|
+
content, tool_calls, usage, finish = await self._stream(kwargs, forced_tool, on_token, on_thinking)
|
|
287
359
|
else:
|
|
288
360
|
content, tool_calls, usage, finish = await self._nonstream(kwargs, forced_tool)
|
|
289
361
|
except LLMError:
|
|
@@ -309,13 +381,22 @@ class AnthropicClient:
|
|
|
309
381
|
}
|
|
310
382
|
return content, tool_calls, usage, response.stop_reason
|
|
311
383
|
|
|
312
|
-
async def _stream(self, kwargs: dict, forced_tool: str | None, on_token) -> tuple:
|
|
384
|
+
async def _stream(self, kwargs: dict, forced_tool: str | None, on_token, on_thinking) -> tuple:
|
|
313
385
|
content = ""
|
|
314
386
|
tool_calls: list[dict[str, Any]] = []
|
|
315
387
|
async with self._client.messages.stream(**kwargs) as stream:
|
|
316
|
-
async for
|
|
317
|
-
|
|
318
|
-
|
|
388
|
+
async for event in stream:
|
|
389
|
+
delta = getattr(event, "delta", None)
|
|
390
|
+
dtype = getattr(delta, "type", None)
|
|
391
|
+
if dtype == "thinking_delta":
|
|
392
|
+
piece = getattr(delta, "thinking", None) or ""
|
|
393
|
+
if piece:
|
|
394
|
+
_safe_on_thinking(on_thinking, piece)
|
|
395
|
+
elif dtype == "text_delta":
|
|
396
|
+
text = getattr(delta, "text", "") or ""
|
|
397
|
+
if text:
|
|
398
|
+
content += text
|
|
399
|
+
_safe_on_token(on_token, text)
|
|
319
400
|
final = await stream.get_final_message()
|
|
320
401
|
for block in final.content:
|
|
321
402
|
if block.type == "tool_use":
|
|
@@ -328,15 +409,36 @@ class AnthropicClient:
|
|
|
328
409
|
}
|
|
329
410
|
return content, tool_calls, usage, final.stop_reason
|
|
330
411
|
|
|
331
|
-
# ---
|
|
412
|
+
# --- 多轮底层接口(首个正当消费端 = agent 工具循环;spec 翻译 / 对齐检查等 LLM 调用亦复用) -------------
|
|
332
413
|
|
|
333
414
|
def _convert_messages(self, messages: list[Message]) -> tuple[str | None, list[dict[str, Any]]]:
|
|
334
|
-
"""分离 system
|
|
415
|
+
"""分离 system 消息并转换其余消息;工具循环形状:assistant+tool_calls →
|
|
416
|
+
tool_use 内容块,tool 角色 → tool_result 用户消息块(Anthropic 契约)。
|
|
417
|
+
连续 tool 消息聚合为一条 user 消息(多个 tool_result 块)——Anthropic 要求
|
|
418
|
+
消息角色交替,逐条拆开会 400(Bedrock 实测)。"""
|
|
335
419
|
system_prompt = None
|
|
336
420
|
api_messages = []
|
|
337
421
|
for msg in messages:
|
|
338
422
|
if msg.role == "system":
|
|
339
423
|
system_prompt = msg.content
|
|
424
|
+
elif msg.role == "assistant" and msg.tool_calls:
|
|
425
|
+
blocks: list[dict[str, Any]] = []
|
|
426
|
+
if msg.content:
|
|
427
|
+
blocks.append({"type": "text", "text": msg.content})
|
|
428
|
+
for tc in msg.tool_calls:
|
|
429
|
+
blocks.append({"type": "tool_use", "id": tc["id"],
|
|
430
|
+
"name": tc["name"], "input": tc.get("arguments", {})})
|
|
431
|
+
api_messages.append({"role": "assistant", "content": blocks})
|
|
432
|
+
elif msg.role == "tool":
|
|
433
|
+
result = {"type": "tool_result", "tool_use_id": msg.tool_call_id or "",
|
|
434
|
+
"content": msg.content}
|
|
435
|
+
last = api_messages[-1] if api_messages else None
|
|
436
|
+
# 连续 tool 消息并入同一条 user 消息(角色交替契约,见 docstring)
|
|
437
|
+
if last and last["role"] == "user" and isinstance(last["content"], list) \
|
|
438
|
+
and last["content"] and last["content"][0]["type"] == "tool_result":
|
|
439
|
+
last["content"].append(result)
|
|
440
|
+
else:
|
|
441
|
+
api_messages.append({"role": "user", "content": [result]})
|
|
340
442
|
else:
|
|
341
443
|
api_messages.append({"role": msg.role, "content": msg.content})
|
|
342
444
|
return system_prompt, api_messages
|
|
@@ -514,6 +616,7 @@ class OpenAIClient:
|
|
|
514
616
|
output_format: dict[str, Any] | None = None,
|
|
515
617
|
notdo: list[str] | None = None,
|
|
516
618
|
on_token: Callable[[str], None] | None = None,
|
|
619
|
+
on_thinking: Callable[[str], None] | None = None,
|
|
517
620
|
api_params: dict[str, Any] | None = None,
|
|
518
621
|
) -> LLMResponse:
|
|
519
622
|
"""单轮调用入口(harness body 用)。
|
|
@@ -523,6 +626,10 @@ class OpenAIClient:
|
|
|
523
626
|
- ``think``:reasoning 模型映射为 ``reasoning_effort``("low"/"medium"/"high",
|
|
524
627
|
dict 可指定 ``effort``;非 reasoning 模型忽略)
|
|
525
628
|
- ``api_params``:透传给 SDK 的额外参数(已知字段入 kwargs,未知入 extra_body)
|
|
629
|
+
- ``on_thinking``:思考/推理增量回调(reasoning_content/reasoning 方言 +
|
|
630
|
+
content 内联 <think> 剥离;Anthropic thinking_delta);流式/非流式路径均
|
|
631
|
+
无条件剥离内联 <think> 标签,返回 content 不含思考文本(含仅传 on_token
|
|
632
|
+
的存量调用);仅传 on_token 时思考增量静默丢弃;回调异常不破主流程
|
|
526
633
|
"""
|
|
527
634
|
self._require_ready()
|
|
528
635
|
model = model or self.config.model
|
|
@@ -562,8 +669,8 @@ class OpenAIClient:
|
|
|
562
669
|
_apply_api_params(kwargs, api_params, _KNOWN_OPENAI_PARAMS)
|
|
563
670
|
|
|
564
671
|
try:
|
|
565
|
-
if on_token:
|
|
566
|
-
content, tool_calls, usage, finish = await self._stream(kwargs, on_token)
|
|
672
|
+
if on_token or on_thinking:
|
|
673
|
+
content, tool_calls, usage, finish = await self._stream(kwargs, on_token, on_thinking)
|
|
567
674
|
else:
|
|
568
675
|
content, tool_calls, usage, finish = await self._nonstream(kwargs)
|
|
569
676
|
except LLMError:
|
|
@@ -576,6 +683,11 @@ class OpenAIClient:
|
|
|
576
683
|
response = await self._client.chat.completions.create(**kwargs)
|
|
577
684
|
choice = response.choices[0]
|
|
578
685
|
content = choice.message.content or ""
|
|
686
|
+
# 内联 <think> 剥离与流式路径对齐:完整文本过一遍剥离器,返回 content 不含思考文本
|
|
687
|
+
stripper = _ThinkTagStripper()
|
|
688
|
+
c_text, _ = stripper.feed(content)
|
|
689
|
+
tail_c, _ = stripper.flush()
|
|
690
|
+
content = c_text + tail_c
|
|
579
691
|
tool_calls: list[dict[str, Any]] = []
|
|
580
692
|
if choice.message.tool_calls:
|
|
581
693
|
for tc in choice.message.tool_calls:
|
|
@@ -590,7 +702,7 @@ class OpenAIClient:
|
|
|
590
702
|
}
|
|
591
703
|
return content, tool_calls, usage, choice.finish_reason
|
|
592
704
|
|
|
593
|
-
async def _stream(self, kwargs: dict, on_token) -> tuple:
|
|
705
|
+
async def _stream(self, kwargs: dict, on_token, on_thinking) -> tuple:
|
|
594
706
|
kwargs["stream"] = True
|
|
595
707
|
# stream_options 仅官方 OpenAI 必然支持;兼容接口(base_url 非空)省略以免被拒
|
|
596
708
|
if not self.config.base_url:
|
|
@@ -599,6 +711,7 @@ class OpenAIClient:
|
|
|
599
711
|
tool_calls: list[dict[str, Any]] = []
|
|
600
712
|
usage: dict[str, int] = {}
|
|
601
713
|
finish: str | None = None
|
|
714
|
+
stripper = _ThinkTagStripper()
|
|
602
715
|
stream = await self._client.chat.completions.create(**kwargs)
|
|
603
716
|
async for chunk in stream:
|
|
604
717
|
if chunk.usage:
|
|
@@ -609,11 +722,26 @@ class OpenAIClient:
|
|
|
609
722
|
if not chunk.choices:
|
|
610
723
|
continue
|
|
611
724
|
delta = chunk.choices[0].delta
|
|
725
|
+
# 思考增量:DeepSeek/Kimi 的 reasoning_content,部分网关用 reasoning;
|
|
726
|
+
# 无原生通道时由 <think> 剥离器从 content 转移(见下)
|
|
727
|
+
reasoning = getattr(delta, "reasoning_content", None) or getattr(delta, "reasoning", None)
|
|
728
|
+
if reasoning:
|
|
729
|
+
_safe_on_thinking(on_thinking, reasoning)
|
|
612
730
|
if delta.content:
|
|
613
|
-
|
|
614
|
-
|
|
731
|
+
c_delta, t_delta = stripper.feed(delta.content)
|
|
732
|
+
content += c_delta # 返回值用剥离后文本(思考不泄入 JSON 输出)
|
|
733
|
+
if c_delta:
|
|
734
|
+
_safe_on_token(on_token, c_delta)
|
|
735
|
+
if t_delta:
|
|
736
|
+
_safe_on_thinking(on_thinking, t_delta)
|
|
615
737
|
if chunk.choices[0].finish_reason:
|
|
616
738
|
finish = chunk.choices[0].finish_reason
|
|
739
|
+
tail_c, tail_t = stripper.flush()
|
|
740
|
+
if tail_c:
|
|
741
|
+
content += tail_c
|
|
742
|
+
_safe_on_token(on_token, tail_c)
|
|
743
|
+
if tail_t:
|
|
744
|
+
_safe_on_thinking(on_thinking, tail_t)
|
|
617
745
|
return content, tool_calls, usage, finish
|
|
618
746
|
|
|
619
747
|
# --- 多轮底层接口 -------------------------------------------------------
|
|
@@ -764,6 +892,7 @@ class RoutingClient:
|
|
|
764
892
|
output_format: dict[str, Any] | None = None,
|
|
765
893
|
notdo: list[str] | None = None,
|
|
766
894
|
on_token: Callable[[str], None] | None = None,
|
|
895
|
+
on_thinking: Callable[[str], None] | None = None,
|
|
767
896
|
api_params: dict[str, Any] | None = None,
|
|
768
897
|
) -> LLMResponse:
|
|
769
898
|
"""单轮调用(harness body 入口),按调用模型路由。"""
|
|
@@ -776,6 +905,7 @@ class RoutingClient:
|
|
|
776
905
|
output_format=output_format,
|
|
777
906
|
notdo=notdo,
|
|
778
907
|
on_token=on_token,
|
|
908
|
+
on_thinking=on_thinking,
|
|
779
909
|
api_params=api_params,
|
|
780
910
|
)
|
|
781
911
|
|
|
@@ -38,6 +38,7 @@ from .infra.events import (
|
|
|
38
38
|
PromptRendered,
|
|
39
39
|
LlmCallStarted,
|
|
40
40
|
LlmToken,
|
|
41
|
+
LlmThinking,
|
|
41
42
|
LlmCallCompleted,
|
|
42
43
|
OutputValidated,
|
|
43
44
|
ImageSaved,
|
|
@@ -104,7 +105,7 @@ from .infra.store import (
|
|
|
104
105
|
)
|
|
105
106
|
# cli 层(CLI 实现,非库面)
|
|
106
107
|
from .cli.command import Command, CommandConfig
|
|
107
|
-
from .cli.entry import ModuleEntry, discover_modules
|
|
108
|
+
from .cli.entry import ModuleEntry, TemplateSpec, discover_modules
|
|
108
109
|
from .cli.loader import ModuleLoader, ModuleManifestError, ModuleRequirementError
|
|
109
110
|
|
|
110
111
|
# 模块对象绑定(`from module_harness import store / submodule / query` 可用;
|
|
@@ -131,6 +132,7 @@ __all__ = [
|
|
|
131
132
|
"PromptRendered",
|
|
132
133
|
"LlmCallStarted",
|
|
133
134
|
"LlmToken",
|
|
135
|
+
"LlmThinking",
|
|
134
136
|
"LlmCallCompleted",
|
|
135
137
|
"OutputValidated",
|
|
136
138
|
"ImageSaved",
|
|
@@ -198,6 +200,7 @@ __all__ = [
|
|
|
198
200
|
"check_resume_compat_from_run",
|
|
199
201
|
# 模块入口(roadmap Phase 0:CLI 使用)
|
|
200
202
|
"ModuleEntry",
|
|
203
|
+
"TemplateSpec",
|
|
201
204
|
"discover_modules",
|
|
202
205
|
# 共享查询层(roadmap Phase 0:CLI/MCP/Web 复用)
|
|
203
206
|
"ReviewEntry",
|