|
|
@@ -143,6 +143,65 @@ function extractToolError(result: unknown): string {
|
|
|
return String(result).slice(0, 500);
|
|
|
}
|
|
|
|
|
|
+function isLikelyFailedToolResult(exec: CollectedToolExec): boolean {
|
|
|
+ if (exec.status === "error") return true;
|
|
|
+ const text = `${exec.error ?? ""}\n${exec.result ?? ""}`.toLowerCase();
|
|
|
+ return /\bfailed\b|\berror\b|失败|异常|出错/.test(text);
|
|
|
+}
|
|
|
+
|
|
|
+function hasSuccessfulSubAgentExec(
|
|
|
+ execs: ReadonlyArray<CollectedToolExec>,
|
|
|
+ agent: string,
|
|
|
+): boolean {
|
|
|
+ return execs.some((exec) =>
|
|
|
+ exec.tool === "sub_agent"
|
|
|
+ && exec.agent === agent
|
|
|
+ && exec.status === "completed"
|
|
|
+ && !isLikelyFailedToolResult(exec)
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+function isWriteNextInstruction(instruction: string): boolean {
|
|
|
+ const trimmed = instruction.trim();
|
|
|
+ return /^(continue|继续|继续写|写下一章|write next|下一章|再来一章)$/i.test(trimmed)
|
|
|
+ || /(继续写|写下一章|下一章|再来一章|write\s+next)/i.test(trimmed);
|
|
|
+}
|
|
|
+
|
|
|
+function looksLikeBookCreatedClaim(responseText: string): boolean {
|
|
|
+ return /(?:已|已经|成功).{0,12}(?:创建|建书|初始化|保存).{0,12}(?:作品|书|书籍|文件夹)?/.test(responseText)
|
|
|
+ || /\b(?:created|initiali[sz]ed|saved)\b.{0,40}\b(?:book|project|novel)\b/i.test(responseText);
|
|
|
+}
|
|
|
+
|
|
|
+function validateAgentActionExecution(args: {
|
|
|
+ readonly instruction: string;
|
|
|
+ readonly agentBookId: string | null | undefined;
|
|
|
+ readonly responseText: string;
|
|
|
+ readonly collectedToolExecs: ReadonlyArray<CollectedToolExec>;
|
|
|
+}): string | undefined {
|
|
|
+ const failedExec = args.collectedToolExecs.find(isLikelyFailedToolResult);
|
|
|
+ if (failedExec) {
|
|
|
+ return `${failedExec.label} 执行失败:${failedExec.error ?? failedExec.result ?? "未知错误"}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ args.agentBookId
|
|
|
+ && isWriteNextInstruction(args.instruction)
|
|
|
+ && !hasSuccessfulSubAgentExec(args.collectedToolExecs, "writer")
|
|
|
+ ) {
|
|
|
+ return "模型声称已完成下一章,但没有实际调用写作工具。请重试;如果仍失败,请检查模型是否支持工具调用。";
|
|
|
+ }
|
|
|
+
|
|
|
+ if (
|
|
|
+ !args.agentBookId
|
|
|
+ && looksLikeBookCreatedClaim(args.responseText)
|
|
|
+ && !resolveCreatedBookIdFromToolExecs(args.collectedToolExecs)
|
|
|
+ ) {
|
|
|
+ return "模型声称已创建作品,但没有实际调用建书工具,也没有生成作品文件。请补充书名/题材后重试,或换用支持工具调用的模型。";
|
|
|
+ }
|
|
|
+
|
|
|
+ return undefined;
|
|
|
+}
|
|
|
+
|
|
|
interface CollectedToolExec {
|
|
|
id: string;
|
|
|
tool: string;
|
|
|
@@ -780,7 +839,7 @@ export function createStudioServer(initialConfig: ProjectConfig, root: string) {
|
|
|
model: overrides?.model ?? currentConfig.llm.model,
|
|
|
projectRoot: root,
|
|
|
defaultLLMConfig: currentConfig.llm,
|
|
|
- foundationReviewRetries: currentConfig.foundation.reviewRetries,
|
|
|
+ foundationReviewRetries: currentConfig.foundation?.reviewRetries ?? 2,
|
|
|
modelOverrides: currentConfig.modelOverrides,
|
|
|
notifyChannels: currentConfig.notify,
|
|
|
logger,
|
|
|
@@ -1940,6 +1999,21 @@ export function createStudioServer(initialConfig: ProjectConfig, root: string) {
|
|
|
instruction,
|
|
|
);
|
|
|
|
|
|
+ if (result.responseText) {
|
|
|
+ const actionExecutionError = validateAgentActionExecution({
|
|
|
+ instruction,
|
|
|
+ agentBookId,
|
|
|
+ responseText: result.responseText,
|
|
|
+ collectedToolExecs,
|
|
|
+ });
|
|
|
+ if (actionExecutionError) {
|
|
|
+ return c.json({
|
|
|
+ error: { code: "AGENT_ACTION_NOT_EXECUTED", message: actionExecutionError },
|
|
|
+ response: actionExecutionError,
|
|
|
+ }, 502);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
let broadcastedCreatedBookId: string | null = null;
|
|
|
const finalizeCreatedBook = async (): Promise<string | null> => {
|
|
|
if (agentBookId) return null;
|
|
|
@@ -2000,6 +2074,18 @@ export function createStudioServer(initialConfig: ProjectConfig, root: string) {
|
|
|
{ maxTokens: 256 },
|
|
|
);
|
|
|
if (fallback.content?.trim()) {
|
|
|
+ const actionExecutionError = validateAgentActionExecution({
|
|
|
+ instruction,
|
|
|
+ agentBookId,
|
|
|
+ responseText: fallback.content,
|
|
|
+ collectedToolExecs,
|
|
|
+ });
|
|
|
+ if (actionExecutionError) {
|
|
|
+ return c.json({
|
|
|
+ error: { code: "AGENT_ACTION_NOT_EXECUTED", message: actionExecutionError },
|
|
|
+ response: actionExecutionError,
|
|
|
+ }, 502);
|
|
|
+ }
|
|
|
await appendManualSessionMessages(root, bookSession.sessionId, [{
|
|
|
role: "assistant",
|
|
|
content: [{ type: "text", text: fallback.content }],
|