mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
fix: handle empty responses from Chutes AI API
- Add content tracking in ChutesHandler.createMessage() for both DeepSeek and non-DeepSeek models - Throw descriptive error when API returns no content - Add test coverage for empty response scenarios - Fixes #7322
This commit is contained in:
parent
f14e6acaf0
commit
8d2a100a63
2 changed files with 141 additions and 7 deletions
|
|
@ -341,7 +341,10 @@ describe("ChutesHandler", () => {
|
|||
mockCreate.mockClear()
|
||||
mockCreate.mockImplementationOnce(async () => ({
|
||||
[Symbol.asyncIterator]: async function* () {
|
||||
// Empty stream for this test
|
||||
// Yield minimal content to avoid triggering the empty response error
|
||||
yield {
|
||||
choices: [{ delta: { content: "test" } }],
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
|
|
@ -376,11 +379,22 @@ describe("ChutesHandler", () => {
|
|||
|
||||
mockCreate.mockImplementationOnce(() => {
|
||||
return {
|
||||
[Symbol.asyncIterator]: () => ({
|
||||
async next() {
|
||||
return { done: true }
|
||||
},
|
||||
}),
|
||||
[Symbol.asyncIterator]: () => {
|
||||
let called = false
|
||||
return {
|
||||
async next() {
|
||||
if (!called) {
|
||||
called = true
|
||||
// Return minimal content to avoid triggering the empty response error
|
||||
return {
|
||||
done: false,
|
||||
value: { choices: [{ delta: { content: "test" } }] },
|
||||
}
|
||||
}
|
||||
return { done: true }
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -421,4 +435,86 @@ describe("ChutesHandler", () => {
|
|||
const model = handlerWithModel.getModel()
|
||||
expect(model.info.temperature).toBe(0.5)
|
||||
})
|
||||
|
||||
it("should throw an error when API returns no content", async () => {
|
||||
// Mock a stream that returns no content chunks
|
||||
const mockStream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
// Only yield usage data, no content
|
||||
yield {
|
||||
choices: [{ delta: {} }],
|
||||
usage: {
|
||||
prompt_tokens: 100,
|
||||
completion_tokens: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValueOnce(mockStream)
|
||||
|
||||
const systemPrompt = "Test system prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message" }]
|
||||
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
|
||||
await expect(async () => {
|
||||
for await (const chunk of generator) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
}).rejects.toThrow("Chutes API did not return any content")
|
||||
|
||||
// Should have yielded usage before throwing
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({
|
||||
type: "usage",
|
||||
inputTokens: 100,
|
||||
outputTokens: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it("should throw an error for DeepSeek R1 models when API returns no content", async () => {
|
||||
const modelId: ChutesModelId = "deepseek-ai/DeepSeek-R1"
|
||||
const handlerWithModel = new ChutesHandler({
|
||||
apiModelId: modelId,
|
||||
chutesApiKey: "test-chutes-api-key",
|
||||
})
|
||||
|
||||
// Mock a stream that returns no content chunks
|
||||
const mockStream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
// Only yield usage data, no content
|
||||
yield {
|
||||
choices: [{ delta: {} }],
|
||||
usage: {
|
||||
prompt_tokens: 100,
|
||||
completion_tokens: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
mockCreate.mockResolvedValueOnce(mockStream)
|
||||
|
||||
const systemPrompt = "Test system prompt"
|
||||
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message" }]
|
||||
|
||||
const generator = handlerWithModel.createMessage(systemPrompt, messages)
|
||||
const chunks: any[] = []
|
||||
|
||||
await expect(async () => {
|
||||
for await (const chunk of generator) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
}).rejects.toThrow("Chutes API did not return any content")
|
||||
|
||||
// Should have yielded usage before throwing
|
||||
expect(chunks).toHaveLength(1)
|
||||
expect(chunks[0]).toEqual({
|
||||
type: "usage",
|
||||
inputTokens: 100,
|
||||
outputTokens: 0,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ export class ChutesHandler extends BaseOpenAiCompatibleProvider<ChutesModelId> {
|
|||
|
||||
override async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
|
||||
const model = this.getModel()
|
||||
let hasContent = false
|
||||
|
||||
if (model.id.includes("DeepSeek-R1")) {
|
||||
const stream = await this.client.chat.completions.create({
|
||||
|
|
@ -66,6 +67,7 @@ export class ChutesHandler extends BaseOpenAiCompatibleProvider<ChutesModelId> {
|
|||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
if (delta?.content) {
|
||||
hasContent = true
|
||||
for (const processedChunk of matcher.update(delta.content)) {
|
||||
yield processedChunk
|
||||
}
|
||||
|
|
@ -82,10 +84,46 @@ export class ChutesHandler extends BaseOpenAiCompatibleProvider<ChutesModelId> {
|
|||
|
||||
// Process any remaining content
|
||||
for (const processedChunk of matcher.final()) {
|
||||
hasContent = true
|
||||
yield processedChunk
|
||||
}
|
||||
|
||||
// If no content was received, throw an error
|
||||
if (!hasContent) {
|
||||
throw new Error(
|
||||
`${this.providerName} API did not return any content. This may indicate an issue with the API, model configuration, or request parameters.`,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
yield* super.createMessage(systemPrompt, messages)
|
||||
// For non-DeepSeek models, track content and handle empty responses
|
||||
const stream = await this.createStream(systemPrompt, messages)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
const delta = chunk.choices[0]?.delta
|
||||
|
||||
if (delta?.content) {
|
||||
hasContent = true
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens: chunk.usage.prompt_tokens || 0,
|
||||
outputTokens: chunk.usage.completion_tokens || 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no content was received, throw an error
|
||||
if (!hasContent) {
|
||||
throw new Error(
|
||||
`${this.providerName} API did not return any content. This may indicate an issue with the API, model configuration, or request parameters.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue