mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
refactor(agent): return request retry action
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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
|
||||
README.md: 6881b190b96b0be41579cb448c552864d4501268
|
||||
README.zh.md: 840fc0e4e915d4af5185ccc399cd5485cb5bd46d
|
||||
# pnpm run verify-translation-pairing --write packages/compact/compact-basic/README.md
|
||||
README.md: 775355f1ac1a7c79c16f66a5b2489d73df7b960d
|
||||
README.zh.md: 2f7ccc7dd00fa5599d3d6bbe66e81d3312d38dd9
|
||||
|
||||
@@ -17,7 +17,7 @@ This backend owns the compaction policy:
|
||||
- **Convergence** — retry head-checkpoint compaction up to `compactionRetries`; reject a summary that does not shrink its source, and throw if retries cannot return below threshold.
|
||||
- **Summarization** — a direct `llm/stream` call uses the configured provider/model pair and cap, falling back to the latest logged request target and then the agent target, without running the loop-only `agent/request` seam. The call replays the conversation's own system prompt, tools, and shadowed-region messages verbatim and appends the compaction instruction as the final user message, so it reuses the provider's warm prefix cache instead of invalidating it. It sets `GenerateOptions.purpose` to `compaction`, which adapters may forward as request attribution (the DeepSeek adapter sends `x-deepseek-harness-compact: 1`) without touching the model-visible body. Only returned text enters the checkpoint, excluding reasoning and tool calls that would leak private reasoning or create an orphaned call.
|
||||
- **Framing** — the replacement user message marks established checkpoint context with `<compacted-summary>` tags. The raw summary remains on the provenance event, and later automatic cycles merge the prior checkpoint.
|
||||
- **Lifecycle** — `compactRegion()` mutates `agent.session` and records its start, summary, replacement, and end. After asynchronous summarization it rejects a changed surface-node snapshot, while unrelated log-only events may append without invalidating the selected span. The serial `agent/step` listener checks pressure before request derivation. A canonical provider overflow is offered through `agent/request-error` after the failed step; the plugin compacts there and calls `agent.retry()` only after durable surface progress.
|
||||
- **Lifecycle** — `compactRegion()` mutates `agent.session` and records its start, summary, replacement, and end. After asynchronous summarization it rejects a changed surface-node snapshot, while unrelated log-only events may append without invalidating the selected span. The serial `agent/step` listener checks pressure before request derivation. A canonical provider overflow is offered through `agent/request-error` after the failed step; the plugin compacts there and returns a retry action only after durable surface progress.
|
||||
- **Overflow recovery** — provider-confirmed overflow needs no capacity metadata: it bypasses normal pressure and retention, prunes, then attempts one maximal balanced head reduction while leaving the newest indivisible unit. Retry is authorized whenever `surface.replaceGeneration` advances, including when pruning lands before later summary work throws. No replacement, an exhausted target-specific cap, cancellation, or an unknown/noncanonical error preserves the original provider failure.
|
||||
- **Failure handling** — an unmatched `compact/start` is an inert crash marker because no summary replacement landed. A region failure records an error end; the surface remains unchanged unless pruning already landed. Operational pressure failures warn and continue, while overflow-recovery failure preserves the original provider error only when no earlier replacement advanced the surface. Cancellation remains authoritative after any progress.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- **收敛**:最多按 `compactionRetries` 重试头部检查点压缩;拒绝不能缩小源内容的摘要,如果重试仍无法回到阈值以下,则抛出异常。
|
||||
- **摘要**:直接 `llm/stream` 调用使用已配置的提供方/模型对与上限,回退到最新已记录请求目标,然后再回退到 agent(智能体)目标,而不运行仅用于 loop 的 `agent/request` seam。该调用会逐字回放会话自身的系统提示词、工具与已遮蔽区域消息,并将压缩指令作为最后一条 user 消息追加,从而复用提供方的热前缀 cache,而非使它失效。它将 `GenerateOptions.purpose` 设为 `compaction`,适配器可将其作为请求归因转发(DeepSeek 适配器发送 `x-deepseek-harness-compact: 1`),但不会触碰模型可见主体。只有返回文本会进入检查点;会排除可能泄露私有推理或产生遗留调用的 reasoning 与工具调用。
|
||||
- **框定**:替换 user 消息使用 `<compacted-summary>` 标签标记已建立的检查点上下文。原始摘要保留在溯源事件上,后续自动周期会合并之前的检查点。
|
||||
- **生命周期**:`compactRegion()` 会更改 `agent.session`,并记录开始、摘要、替换与结束。异步摘要后,它会拒绝已改变的表层节点快照,而不相关的仅日志事件可以追加,不会使已选 span 失效。串行 `agent/step` listener 会在派生请求之前检查压力。规范提供方溢出会在失败步骤之后经由 `agent/request-error` 交给本插件;插件在此执行压缩,并且只在表层取得持久进展后才调用 `agent.retry()`。
|
||||
- **生命周期**:`compactRegion()` 会更改 `agent.session`,并记录开始、摘要、替换与结束。异步摘要后,它会拒绝已改变的表层节点快照,而不相关的仅日志事件可以追加,不会使已选 span 失效。串行 `agent/step` listener 会在派生请求之前检查压力。规范提供方溢出会在失败步骤之后经由 `agent/request-error` 交给本插件;插件在此执行压缩,并且只在表层取得持久进展后才返回重试动作。
|
||||
- **溢出恢复**:提供方已确认的溢出不需容量元数据。它会绕过常规压力与保留,执行剪枝,再尝试一次最大平衡头部缩减,并留下最新不可分单元。只要 `surface.replaceGeneration` 前进,就允许重试,包括剪枝在后续摘要工作抛出异常前已落地的情况。如果没有替换、精确目标上限已耗尽、已取消,或遇到未知/非规范错误,则保留原始提供方失败。
|
||||
- **失败处理**:不匹配的 `compact/start` 是惰性崩溃标记,因为没有摘要替换落地。区域失败会记录错误结束;除非剪枝已落地,否则表层保持不变。操作性压力失败会发出警告并继续;只当之前没有替换使表层前进时,溢出恢复失败才保留原始提供方错误。取消在任何进展后仍具有最高权威。
|
||||
|
||||
|
||||
@@ -200,8 +200,7 @@ export class BasicCompactService extends CompactService {
|
||||
+ 'retrying from the replacement surface',
|
||||
)
|
||||
this.overflowRetries.set(agent, retries + 1)
|
||||
agent.retry()
|
||||
return
|
||||
return { kind: 'retry' }
|
||||
}
|
||||
ctx.logger.warn(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- the signal can abort while recovery is awaited.
|
||||
@@ -216,7 +215,7 @@ export class BasicCompactService extends CompactService {
|
||||
|| agent.session.surface.replaceGeneration <= generation) return next()
|
||||
if (result !== null) logResult(result, 'context overflow recovery')
|
||||
this.overflowRetries.set(agent, retries + 1)
|
||||
agent.retry()
|
||||
return { kind: 'retry' }
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import type {
|
||||
} from '@deepseek-ai/dsh-llm'
|
||||
import { Session, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import TokenMeterService from '@deepseek-ai/dsh-token-meter'
|
||||
import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { agentEvents, type Agent, type RequestErrorAction } from '@deepseek-ai/dsh-agent'
|
||||
import ToolResultPruneService from '@deepseek-ai/dsh-compact-tool-result-prune'
|
||||
|
||||
const SIGNAL = new AbortController().signal
|
||||
@@ -79,7 +79,6 @@ function agent(session: Session, model?: string): Agent {
|
||||
return {
|
||||
session,
|
||||
options: model === undefined ? {} : { provider: model, model },
|
||||
retry() {},
|
||||
} as Agent
|
||||
}
|
||||
|
||||
@@ -1292,15 +1291,13 @@ describe('automatic listener and loader composition', () => {
|
||||
owner: Agent,
|
||||
error: Error & { code?: string },
|
||||
signal = SIGNAL,
|
||||
next: () => Promise<void> = () => Promise.resolve(),
|
||||
next: () => Promise<RequestErrorAction> = () => Promise.resolve(undefined),
|
||||
): Promise<boolean> {
|
||||
const failure: LlmFailure = { message: error.message, code: error.code ?? 'UNKNOWN' }
|
||||
const turn = owner.session.events.findLast(event => event.type === 'turn/start')?.data.turn ?? 1
|
||||
let retried = false
|
||||
owner.retry = () => { retried = true }
|
||||
return agentEvents(ctx, owner).waterfall(
|
||||
'agent/request-error', turn, 1, error, failure, signal, next,
|
||||
).then(() => retried)
|
||||
).then(action => action?.kind === 'retry')
|
||||
}
|
||||
|
||||
function overflow(message = 'provider overflow'): Error & { code: string } {
|
||||
@@ -1584,7 +1581,7 @@ describe('automatic listener and loader composition', () => {
|
||||
|
||||
const decision = await recover(ctx, agent(session, MODEL), original, SIGNAL, () => {
|
||||
delegations += 1
|
||||
return Promise.resolve()
|
||||
return Promise.resolve(undefined)
|
||||
})
|
||||
|
||||
expect(decision).toBe(false)
|
||||
|
||||
Reference in New Issue
Block a user