fix: continue Pascal agents until a scene write

This commit is contained in:
134 2026-07-12 10:43:33 +08:00
parent cc6c4b547c
commit 4f5dbb4589

View File

@ -1,5 +1,13 @@
import { createOpenAI } from '@ai-sdk/openai'
import { convertToModelMessages, stepCountIs, streamText, type UIMessage } from 'ai'
import {
convertToModelMessages,
createUIMessageStream,
createUIMessageStreamResponse,
type ModelMessage,
stepCountIs,
streamText,
type UIMessage,
} from 'ai'
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { validatePascalAgentAccess } from '@/lib/pascal-agent-access'
@ -24,7 +32,9 @@ const requestSchema = z.object({
})
const AGENT_ERROR_MESSAGE = 'AI design agent is temporarily unavailable. Please try again.'
const AGENT_NO_MUTATION_MESSAGE = '模型未执行任何建模动作,请重试或换用支持工具调用的多模态模型。'
const SCENE_ERROR_MESSAGE = 'The current scene could not be loaded.'
const MAX_MUTATION_CONTINUATIONS = 2
function accessErrorResponse(result: ReturnType<typeof validatePascalAgentAccess>) {
if (result === 'missing_proxy_secret') {
@ -114,6 +124,11 @@ export async function POST(request: Request) {
baseURL: config.baseUrl,
name: 'pascal-agent',
})
const initialMessages = convertToModelMessages(parsed.data.messages as UIMessage[])
const systemPrompt = buildPascalAgentPrompt({
sceneId: parsed.data.sceneId,
sceneName: session.sceneName,
})
let finalized = false
const finalize = async (save: boolean) => {
@ -134,36 +149,91 @@ export async function POST(request: Request) {
}
}
try {
const result = streamText({
const runAgent = (messages: ModelMessage[], mutationOnly: boolean) =>
streamText({
abortSignal,
...(mutationOnly
? {
activeTools: session.mutationToolNames,
toolChoice: 'required' as const,
}
: {
prepareStep: ({ stepNumber }) =>
pascalAgentStepPolicy({
hasMutation: session.hasMutation(),
mutationRequired,
mutationToolNames: session.mutationToolNames,
stepNumber,
}),
}),
maxOutputTokens: 1600,
messages: convertToModelMessages(parsed.data.messages as UIMessage[]),
messages,
model: provider.chat(config.model),
onAbort: async () => finalize(false),
onError: async () => finalize(false),
onFinish: async () => finalize(session.hasMutation()),
prepareStep: ({ stepNumber }) =>
pascalAgentStepPolicy({
hasMutation: session.hasMutation(),
mutationRequired,
mutationToolNames: session.mutationToolNames,
stepNumber,
}),
stopWhen: stepCountIs(config.maxSteps),
system: buildPascalAgentPrompt({
sceneId: parsed.data.sceneId,
sceneName: session.sceneName,
}),
system: systemPrompt,
temperature: 0.2,
tools: session.tools,
})
return result.toUIMessageStreamResponse({
onError: (error) => {
console.error('Pascal agent stream failed.', error)
return AGENT_ERROR_MESSAGE
},
const streamErrorText = (error: unknown) => {
console.error('Pascal agent stream failed.', error)
return AGENT_ERROR_MESSAGE
}
const stream = createUIMessageStream<UIMessage>({
execute: async ({ writer }) => {
let messages = initialMessages
let sendStart = true
let finishReason: Awaited<ReturnType<typeof runAgent>['finishReason']> | undefined
try {
for (let attempt = 0; attempt <= MAX_MUTATION_CONTINUATIONS; attempt++) {
const mutationOnly = attempt > 0
const result = runAgent(messages, mutationOnly)
for await (const chunk of result.toUIMessageStream<UIMessage>({
onError: streamErrorText,
sendFinish: false,
sendStart,
})) {
writer.write(chunk)
}
finishReason = await result.finishReason
const response = await result.response
if (!mutationRequired || session.hasMutation()) break
messages = [
...messages,
...response.messages,
{
role: 'user',
content:
'You only inspected the scene and did not change it. Continue now. Do not explain or finish. Call one of the available scene mutation tools with valid arguments, then verify the persisted result.',
},
]
sendStart = false
}
if (mutationRequired && !session.hasMutation()) {
console.error(
'Pascal agent exhausted mutation continuations without changing the scene.',
{
sceneId: parsed.data.sceneId,
},
)
writer.write({ type: 'error', errorText: AGENT_NO_MUTATION_MESSAGE })
}
writer.write({ type: 'finish', finishReason })
} finally {
await finalize(session.hasMutation())
}
},
onError: streamErrorText,
})
try {
return createUIMessageStreamResponse({
stream,
})
} catch (error) {
await finalize(false)