fix(core): enforce one step settlement
This commit is contained in:
parent
59e8dbb066
commit
db664db5f3
8 changed files with 71 additions and 43 deletions
|
|
@ -307,7 +307,7 @@ export const layer = Layer.effect(
|
|||
yield* withPublication(publisher.failUnsettledTools(`Tool execution failed: ${message}`))
|
||||
}
|
||||
const stepSettlement = publisher.stepSettlement()
|
||||
if (stepSettlement && !publisher.hasProviderError()) {
|
||||
if (stepSettlement && !publisher.hasProviderError() && !publisher.hasAssistantSettled()) {
|
||||
const endSnapshot = yield* snapshots.capture()
|
||||
const files =
|
||||
startSnapshot && endSnapshot
|
||||
|
|
|
|||
|
|
@ -66,15 +66,13 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
>()
|
||||
const timestamp = DateTime.now
|
||||
let assistantMessageID: SessionMessage.ID | undefined
|
||||
let assistantActive = false
|
||||
let assistantSettled = false
|
||||
let providerFailed = false
|
||||
let stepSettlement: { readonly finish: string; readonly tokens: ReturnType<typeof tokens> } | undefined
|
||||
let stepSettlement: { readonly finish: SessionMessage.Finish; readonly tokens: ReturnType<typeof tokens> } | undefined
|
||||
|
||||
const startAssistant = Effect.fnUntraced(function* () {
|
||||
if (assistantMessageID !== undefined) return assistantMessageID
|
||||
assistantMessageID = SessionMessage.ID.create()
|
||||
assistantActive = true
|
||||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
...input,
|
||||
assistantMessageID,
|
||||
|
|
@ -196,32 +194,38 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
yield* flushFragments()
|
||||
})
|
||||
|
||||
const failAssistant = Effect.fnUntraced(function* (message: string) {
|
||||
const settleAssistant = Effect.fnUntraced(function* (
|
||||
publish: (assistantMessageID: SessionMessage.ID) => Effect.Effect<void>,
|
||||
) {
|
||||
if (assistantSettled) return
|
||||
yield* flush()
|
||||
const assistantMessageID = yield* startAssistant()
|
||||
assistantActive = false
|
||||
assistantSettled = true
|
||||
yield* events.publish(SessionEvent.Step.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID,
|
||||
error: { type: "unknown", message },
|
||||
})
|
||||
yield* publish(assistantMessageID)
|
||||
})
|
||||
|
||||
const interruptAssistant = Effect.fnUntraced(function* () {
|
||||
if (assistantSettled) return
|
||||
yield* flush()
|
||||
const assistantMessageID = yield* startAssistant()
|
||||
assistantActive = false
|
||||
assistantSettled = true
|
||||
yield* events.publish(SessionEvent.Step.Interrupted, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID,
|
||||
})
|
||||
})
|
||||
const failAssistant = (message: string) =>
|
||||
settleAssistant((assistantMessageID) =>
|
||||
Effect.gen(function* () {
|
||||
yield* events.publish(SessionEvent.Step.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID,
|
||||
error: { type: "unknown", message },
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
const interruptAssistant = () =>
|
||||
settleAssistant((assistantMessageID) =>
|
||||
Effect.gen(function* () {
|
||||
yield* events.publish(SessionEvent.Step.Interrupted, {
|
||||
sessionID: input.sessionID,
|
||||
timestamp: yield* timestamp,
|
||||
assistantMessageID,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
const failUnsettledTools = Effect.fn("SessionRunner.failUnsettledTools")(function* (
|
||||
message: string,
|
||||
|
|
@ -408,7 +412,6 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
}
|
||||
case "step-finish":
|
||||
yield* flush()
|
||||
assistantActive = false
|
||||
if (stepSettlement) return yield* Effect.die("Duplicate step finish")
|
||||
stepSettlement = { finish: event.reason, tokens: tokens(event.usage) }
|
||||
return
|
||||
|
|
@ -427,8 +430,8 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
|||
failAssistant,
|
||||
interruptAssistant,
|
||||
failUnsettledTools,
|
||||
hasActiveAssistant: () => assistantActive,
|
||||
hasAssistantStarted: () => assistantMessageID !== undefined,
|
||||
hasAssistantSettled: () => assistantSettled,
|
||||
hasProviderError: () => providerFailed,
|
||||
stepSettlement: () => stepSettlement,
|
||||
startAssistant,
|
||||
|
|
|
|||
|
|
@ -543,13 +543,22 @@ const verifyPartialFlushOnInterruption = (kind: FragmentKind) =>
|
|||
yield* Deferred.await(streamed)
|
||||
yield* Fiber.interrupt(fiber)
|
||||
const { db } = yield* Database.Service
|
||||
const interrupted = yield* db
|
||||
const interruptedVersion = SessionEvent.Step.Interrupted.durable?.version
|
||||
expect(interruptedVersion).toBe(2)
|
||||
if (interruptedVersion === undefined) return yield* Effect.die("Step.Interrupted must be durable")
|
||||
const settlements = yield* db
|
||||
.select({ type: EventTable.type })
|
||||
.from(EventTable)
|
||||
.where(eq(EventTable.type, EventV2.versionedType(SessionEvent.Step.Interrupted.type, 1)))
|
||||
.where(eq(EventTable.aggregate_id, sessionID))
|
||||
.all()
|
||||
.pipe(Effect.orDie)
|
||||
expect(interrupted).toHaveLength(1)
|
||||
expect(
|
||||
settlements.filter(({ type }) =>
|
||||
[SessionEvent.Step.Ended.type, SessionEvent.Step.Failed.type, SessionEvent.Step.Interrupted.type].some((settled) =>
|
||||
type.startsWith(settled),
|
||||
),
|
||||
),
|
||||
).toEqual([{ type: EventV2.versionedType(SessionEvent.Step.Interrupted.type, interruptedVersion) }])
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: prompt },
|
||||
{
|
||||
|
|
@ -2660,6 +2669,7 @@ describe("SessionRunnerLLM", () => {
|
|||
state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } },
|
||||
},
|
||||
],
|
||||
finish: "interrupted",
|
||||
},
|
||||
])
|
||||
}),
|
||||
|
|
@ -2804,6 +2814,7 @@ describe("SessionRunnerLLM", () => {
|
|||
state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } },
|
||||
},
|
||||
],
|
||||
finish: "interrupted",
|
||||
},
|
||||
])
|
||||
}),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue