Skip to content

Commit 35b0848

Browse files
author
MouseWW
committed
fix(llm): reject incomplete responses
1 parent 4599b3a commit 35b0848

4 files changed

Lines changed: 57 additions & 9 deletions

File tree

RELEASE_NOTES.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
# Anything Analyzer v3.6.18
1+
# Anything Analyzer v3.6.19
22

33
## 修复
44

5-
- **Responses API 流式失败信息更准确** — 修复 `response.failed` SSE 将嵌套错误消息显示为 `Unknown stream error` 的问题
6-
- 兼容 `response.error.message` 结构,保留现有顶层 `message``error.message` 处理
7-
- 增加嵌套失败事件回归测试,确保模型过载等错误能直接反馈给调用方
5+
- **Responses API incomplete 状态显式报错** — 修复 `status: "incomplete"``response.incomplete` SSE 被当作成功响应的问题
6+
- 非流式响应会携带 `incomplete_details.reason` 抛出明确错误
7+
- 流式响应遇到 `response.incomplete` 会停止并返回截断原因,避免误用部分输出
88

99
## 下载
1010

1111
| 平台 | 文件 |
1212
|------|------|
13-
| Windows | Anything-Analyzer-Setup-3.6.18.exe |
14-
| macOS (Apple Silicon) | Anything-Analyzer-3.6.18-arm64.dmg |
15-
| macOS (Intel) | Anything-Analyzer-3.6.18-x64.dmg |
16-
| Linux | Anything-Analyzer-3.6.18.AppImage |
13+
| Windows | Anything-Analyzer-Setup-3.6.19.exe |
14+
| macOS (Apple Silicon) | Anything-Analyzer-3.6.19-arm64.dmg |
15+
| macOS (Intel) | Anything-Analyzer-3.6.19-x64.dmg |
16+
| Linux | Anything-Analyzer-3.6.19.AppImage |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "anything-analyzer",
3-
"version": "3.6.18",
3+
"version": "3.6.19",
44
"description": "Universal web protocol analyzer with embedded browser and AI-powered analysis",
55
"packageManager": "pnpm@10.24.0",
66
"main": "./out/main/index.js",

src/main/ai/llm-router.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ interface ToolCall {
2222
function: { name: string; arguments: string };
2323
}
2424

25+
interface ResponsesIncompleteDetails {
26+
reason?: string;
27+
}
28+
2529
// Anthropic content block types
2630
interface AnthropicTextBlock {
2731
type: "text";
@@ -561,9 +565,14 @@ export class LLMRouter {
561565
if (stream) return this.parseResponsesStream(response, onChunk!);
562566

563567
const data = await this.safeParseJson<{
568+
status?: string;
569+
incomplete_details?: ResponsesIncompleteDetails;
564570
output_text?: string;
565571
usage?: { input_tokens: number; output_tokens: number };
566572
}>(response);
573+
if (data.status === "incomplete") {
574+
throw new Error(`Responses API incomplete: ${data.incomplete_details?.reason || "unknown"}`);
575+
}
567576
return {
568577
content: data.output_text || "",
569578
promptTokens: data.usage?.input_tokens || 0,
@@ -707,6 +716,10 @@ export class LLMRouter {
707716
promptTokens = parsed.response.usage.input_tokens || 0;
708717
completionTokens = parsed.response.usage.output_tokens || 0;
709718
}
719+
if (currentEvent === "response.incomplete") {
720+
const reason = parsed.response?.incomplete_details?.reason || "unknown";
721+
throw new Error(`Responses API incomplete: ${reason}`);
722+
}
710723
if (currentEvent === "error" || currentEvent === "response.failed") {
711724
const errorMsg =
712725
parsed.message ||

tests/main/ai/llm-router.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,23 @@ describe("LLMRouter", () => {
255255
expect(result.promptTokens).toBe(100);
256256
expect(result.completionTokens).toBe(200);
257257
});
258+
259+
it("should reject incomplete Responses API results", async () => {
260+
const config: LLMProviderConfig = { ...baseConfig, apiType: "responses" };
261+
fetchSpy.mockResolvedValueOnce(
262+
createJSONResponse({
263+
status: "incomplete",
264+
incomplete_details: { reason: "max_output_tokens" },
265+
output_text: "partial",
266+
}),
267+
);
268+
269+
const router = new LLMRouter(config);
270+
271+
await expect(
272+
router.complete([{ role: "user", content: "test" }]),
273+
).rejects.toThrow("Responses API incomplete: max_output_tokens");
274+
});
258275
});
259276

260277
describe("completeResponses - streaming", () => {
@@ -361,6 +378,24 @@ describe("LLMRouter", () => {
361378
router.complete([{ role: "user", content: "test" }], () => {}),
362379
).rejects.toThrow("Responses API stream error: model overloaded");
363380
});
381+
382+
it("should reject when Responses API stream emits an incomplete event", async () => {
383+
const config: LLMProviderConfig = { ...baseConfig, apiType: "responses" };
384+
fetchSpy.mockResolvedValueOnce(
385+
createSSEResponse([
386+
{
387+
event: "response.incomplete",
388+
data: '{"response":{"incomplete_details":{"reason":"max_output_tokens"}}}',
389+
},
390+
]),
391+
);
392+
393+
const router = new LLMRouter(config);
394+
395+
await expect(
396+
router.complete([{ role: "user", content: "test" }], () => {}),
397+
).rejects.toThrow("Responses API incomplete: max_output_tokens");
398+
});
364399
});
365400

366401
describe("completeOpenAI - streaming", () => {

0 commit comments

Comments
 (0)