refactor: remove per-followup result attribution
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write python/sdk/README.md
|
||||
README.md: f1e16e724efd6f71f63e475e47d7e4d704b8ceac
|
||||
README.zh.md: 11ff235a0eea23acab5f1737537c799021c35bea
|
||||
README.md: 81bac18690665e4f48e6edb0a7053d7979e613df
|
||||
README.zh.md: 8ce2fe85ec38327ddc9183661c3b26fe97dfa630
|
||||
@@ -35,7 +35,9 @@ with DeepSeekHarness(
|
||||
|
||||
`provider` selects a provider route registered by the chosen Cordis composition; `model` is the model id resolved by that adapter. `max_tokens` is an optional positive per-request output-token cap for the root agent and its in-process descendants; omission leaves the provider default in control. Compaction summaries keep the separate limit configured by their compaction plugin. The bundled default composition registers `deepseek`. A custom composition can mount `llm-pi-ai`, configure provider-specific credentials/endpoints there, and select any provider/model present in pi-ai's installed catalog.
|
||||
|
||||
`HarnessClient` retains discovered subagent ancestry for the lifetime of the runtime process. During each `Session.run()`, `TurnResult.notifications` and `on_notification` receive the root session and all known descendant notifications in wire order, including nested subagent lifecycle and session events. `TurnResult.events` remains the root session's complete event stream, and `TurnResult.final_response` is the text content from its last `assistant/message`; descendant messages therefore cannot replace the root response.
|
||||
`Session.run()` owns an activity interval from its prompt's durable inbox receipt through the next whole-agent idle and returns `RunResult(session_id, final_response, events, notifications, session_root)`. The result has no prompt-level status or turn reason: `final_response` is the last committed root-session assistant text in the interval, not an output causally assigned to the prompt. Steering, injected context, and other queued work may contribute before idle.
|
||||
|
||||
`HarnessClient` retains discovered subagent ancestry for the lifetime of the runtime process. During each `Session.run()`, `RunResult.notifications` and `on_notification` receive the root session and all known descendant notifications in wire order, including nested subagent lifecycle and session events. `RunResult.events` contains root-session events only, so descendant messages cannot replace the root response. The low-level `session_prompt()` returns the queued `MessageId` immediately; callers that bypass `Session.run()` own any later activity boundary themselves.
|
||||
|
||||
The same behavior can be selected for the runtime subprocess with `DSH_CORDIS_CONFIG`. The injection lives in `HarnessClient.start()`, so the low-level client's default launch gets it too: when the launch resolves to the bundled runtime and neither `cordis` nor a non-empty `DSH_CORDIS_CONFIG` is set (the runtime treats an empty value as absent, and so does the injection check), the bundled default configuration is used; an explicit `runtime_bin`, `bridge_bin`, or `launch_args_override` disables the injection entirely. See the [sdk-runtime README](../sdk-runtime/README.md) for the runtime carriers (production exe vs dev-only node closure) and how to obtain them.
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ with DeepSeekHarness(
|
||||
|
||||
`provider` 用于选择当前 Cordis 组合已注册的提供方路由;`model` 是该适配器解析的模型 ID。`max_tokens` 是可选的正整数,用于限制根 agent 及其进程内后代每次请求的输出 token;省略时由提供方默认值控制。压缩摘要继续使用压缩插件单独配置的上限。内置默认组合注册 `deepseek`。自定义组合可以挂载 `llm-pi-ai`,在其中配置各提供方的凭据与端点,再选择 pi-ai 已安装目录中的任意提供方/模型组合。
|
||||
|
||||
`HarnessClient` 会在运行时进程的生命周期内保留已发现的 subagent(子 agent)祖先关系。每次执行 `Session.run()` 时,`TurnResult.notifications` 与 `on_notification` 会按协议传输顺序收到根会话及所有已知后代的通知,其中包括嵌套 subagent 的生命周期事件与会话事件。`TurnResult.events` 仍只保存根会话的完整事件流,`TurnResult.final_response` 则取该会话最后一个 `assistant/message` 的文本内容,因此后代消息不会覆盖根会话回复。
|
||||
`Session.run()` 拥有一个从提示词的持久 inbox 回执开始、到整个 agent 下一次进入 idle 为止的活动区间,并返回 `RunResult(session_id, final_response, events, notifications, session_root)`。结果不携带提示词级状态或轮次原因:`final_response` 是该区间内根会话最后提交的助手文本,并非因果上归属于该提示词的输出。steering(中途引导)、注入的上下文和其他排队工作都可能在 idle 前参与其中。
|
||||
|
||||
`HarnessClient` 会在运行时进程的生命周期内保留已发现的 subagent(子 agent)祖先关系。每次执行 `Session.run()` 时,`RunResult.notifications` 与 `on_notification` 会按协议传输顺序收到根会话及所有已知后代的通知,其中包括嵌套 subagent 的生命周期事件与会话事件。`RunResult.events` 只包含根会话事件,因此后代消息不会覆盖根会话回复。底层 `session_prompt()` 会立即返回已排队消息的 `MessageId`;绕过 `Session.run()` 的调用方必须自行负责后续的活动边界。
|
||||
|
||||
同样的行为也可以通过 `DSH_CORDIS_CONFIG` 为运行时子进程选定。注入逻辑位于 `HarnessClient.start()`,因此底层客户端的默认启动也具有此行为:当启动解析到内置运行时,且 `cordis` 与非空的 `DSH_CORDIS_CONFIG` 均未设置时(运行时把空值视为缺省,注入检查与之一致),使用内置的默认配置;显式给出 `runtime_bin`、`bridge_bin` 或 `launch_args_override` 则完全禁用注入。运行时载体(生产用 exe 与仅限开发的 `node` 闭包)及其获取方式见 [sdk-runtime README](../sdk-runtime/README.md)。
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .api import DeepSeekHarness, DeepSeekHarnessConfig, Session, TurnResult
|
||||
from .api import DeepSeekHarness, DeepSeekHarnessConfig, RunResult, Session
|
||||
from .client import HarnessClient, HarnessConfig
|
||||
from .models import IncomingRequest, InitializeResponse, JsonObject, Notification, ServerInfo
|
||||
|
||||
@@ -6,7 +6,7 @@ __all__ = [
|
||||
"DeepSeekHarness",
|
||||
"DeepSeekHarnessConfig",
|
||||
"Session",
|
||||
"TurnResult",
|
||||
"RunResult",
|
||||
"HarnessClient",
|
||||
"HarnessConfig",
|
||||
"IncomingRequest",
|
||||
|
||||
@@ -35,9 +35,8 @@ class DeepSeekHarnessConfig:
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class TurnResult:
|
||||
class RunResult:
|
||||
session_id: str
|
||||
status: str
|
||||
final_response: str
|
||||
events: list[JsonObject]
|
||||
notifications: list[Notification]
|
||||
@@ -119,7 +118,7 @@ class DeepSeekHarness:
|
||||
*,
|
||||
session_id: str | None = None,
|
||||
on_notification: Callable[[Notification], None] | None = None,
|
||||
) -> TurnResult:
|
||||
) -> RunResult:
|
||||
return self.start_session(session_id).run(input, on_notification=on_notification)
|
||||
|
||||
|
||||
@@ -133,15 +132,12 @@ class Session:
|
||||
input: str | list[JsonObject],
|
||||
*,
|
||||
on_notification: Callable[[Notification], None] | None = None,
|
||||
) -> TurnResult:
|
||||
) -> RunResult:
|
||||
content_blocks = normalize_input(input)
|
||||
notifications: list[Notification] = []
|
||||
events: list[JsonObject] = []
|
||||
status = "error"
|
||||
finished = False
|
||||
|
||||
def collect(notification: Notification) -> None:
|
||||
nonlocal finished, status
|
||||
notifications.append(notification)
|
||||
if on_notification is not None:
|
||||
on_notification(notification)
|
||||
@@ -152,25 +148,31 @@ class Session:
|
||||
event = notification.payload.get("event")
|
||||
if isinstance(event, dict):
|
||||
events.append(event)
|
||||
if notification.method == "session.finished" and notification.payload.get("sessionId") == self.id:
|
||||
status = str(notification.payload.get("status") or "ok")
|
||||
finished = True
|
||||
|
||||
with self.harness.client.subscribe_session_notifications(self.id) as subscription:
|
||||
self.harness.client.session_prompt(
|
||||
message_id = self.harness.client.session_prompt(
|
||||
self.id,
|
||||
content_blocks,
|
||||
on_notification=collect,
|
||||
notification_subscription=subscription,
|
||||
)
|
||||
|
||||
while not finished:
|
||||
received = False
|
||||
while True:
|
||||
notification = subscription.next()
|
||||
if not received:
|
||||
if not _is_inbox_receipt(notification, self.id, message_id):
|
||||
continue
|
||||
received = True
|
||||
collect(notification)
|
||||
if (
|
||||
notification.method == "session.status"
|
||||
and notification.payload.get("sessionId") == self.id
|
||||
and notification.payload.get("status") == "idle"
|
||||
):
|
||||
break
|
||||
|
||||
return TurnResult(
|
||||
return RunResult(
|
||||
session_id=self.id,
|
||||
status=status,
|
||||
final_response=final_response(events),
|
||||
events=events,
|
||||
notifications=notifications,
|
||||
@@ -178,6 +180,19 @@ class Session:
|
||||
)
|
||||
|
||||
|
||||
def _is_inbox_receipt(notification: Notification, session_id: str, message_id: str) -> bool:
|
||||
if notification.method != "session.event" or notification.payload.get("sessionId") != session_id:
|
||||
return False
|
||||
event = notification.payload.get("event")
|
||||
if not isinstance(event, dict) or event.get("type") != "agent/inbox/spliced":
|
||||
return False
|
||||
data = event.get("data")
|
||||
inserted = data.get("inserted") if isinstance(data, dict) else None
|
||||
return isinstance(inserted, list) and any(
|
||||
isinstance(message, dict) and message.get("id") == message_id for message in inserted
|
||||
)
|
||||
|
||||
|
||||
def normalize_input(input: str | list[JsonObject]) -> list[JsonObject]:
|
||||
if isinstance(input, str):
|
||||
return [{"type": "text", "text": input}]
|
||||
|
||||
@@ -10,7 +10,7 @@ import uuid
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Callable, Literal, TypeAlias, TypeVar
|
||||
from typing import Callable, TypeAlias, TypeVar
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -142,9 +142,9 @@ class HarnessClient:
|
||||
*,
|
||||
on_notification: Callable[[Notification], None] | None = None,
|
||||
notification_subscription: "NotificationSubscription | None" = None,
|
||||
) -> None:
|
||||
) -> str:
|
||||
payload: JsonObject = {"sessionId": session_id, "contentBlocks": content_blocks}
|
||||
self.request(
|
||||
response = self.request(
|
||||
"session/prompt",
|
||||
payload,
|
||||
response_model=_SessionPromptResponse,
|
||||
@@ -152,6 +152,7 @@ class HarnessClient:
|
||||
notification_filter=self._notification_belongs_to_session_tree(session_id),
|
||||
notification_subscription=notification_subscription,
|
||||
)
|
||||
return response.messageId
|
||||
|
||||
def request(
|
||||
self,
|
||||
@@ -536,7 +537,7 @@ class NotificationSubscription:
|
||||
|
||||
|
||||
class _SessionPromptResponse(BaseModel):
|
||||
accepted: Literal[True]
|
||||
messageId: str
|
||||
|
||||
|
||||
class _ShutdownResponse(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user