mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Fix #5162: Enhanced VS Code Language Model content filtering error handling
- Added specific error detection for VS Code content filtering scenarios - Implemented helper methods to identify filtering errors in both Error objects and error-like objects - Enhanced error messages to provide clear guidance when content is filtered - Added comprehensive test coverage for content filtering detection - Supports case-insensitive detection of filtering keywords like 'filtered', 'content policy', 'inappropriate', 'safety', 'blocked', etc. - Maintains existing error handling for cancellation and other error types
This commit is contained in:
parent
3a8ba27615
commit
186fe01bfd
2 changed files with 311 additions and 0 deletions
236
src/api/providers/__tests__/vscode-lm-content-filtering.test.ts
Normal file
236
src/api/providers/__tests__/vscode-lm-content-filtering.test.ts
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import * as vscode from "vscode"
|
||||
import { VsCodeLmHandler } from "../vscode-lm"
|
||||
|
||||
// Mock vscode module
|
||||
vi.mock("vscode", () => ({
|
||||
workspace: {
|
||||
onDidChangeConfiguration: vi.fn(() => ({ dispose: vi.fn() })),
|
||||
},
|
||||
lm: {
|
||||
selectChatModels: vi.fn(),
|
||||
},
|
||||
LanguageModelChatMessage: {
|
||||
Assistant: vi.fn((content) => ({ role: "assistant", content })),
|
||||
User: vi.fn((content) => ({ role: "user", content })),
|
||||
},
|
||||
CancellationError: class extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message)
|
||||
this.name = "CancellationError"
|
||||
}
|
||||
},
|
||||
CancellationTokenSource: vi.fn(() => ({
|
||||
token: {},
|
||||
cancel: vi.fn(),
|
||||
dispose: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
describe("VsCodeLmHandler Content Filtering", () => {
|
||||
let handler: VsCodeLmHandler
|
||||
let mockClient: any
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
|
||||
mockClient = {
|
||||
id: "test-model",
|
||||
name: "Test Model",
|
||||
vendor: "test",
|
||||
family: "test",
|
||||
version: "1.0",
|
||||
maxInputTokens: 8192,
|
||||
sendRequest: vi.fn(),
|
||||
countTokens: vi.fn().mockResolvedValue(10),
|
||||
}
|
||||
|
||||
vi.mocked(vscode.lm.selectChatModels).mockResolvedValue([mockClient])
|
||||
|
||||
handler = new VsCodeLmHandler({
|
||||
vsCodeLmModelSelector: { vendor: "test", family: "test" },
|
||||
})
|
||||
})
|
||||
|
||||
describe("Content Filtering Error Detection", () => {
|
||||
it("should detect content filtering errors in Error objects", async () => {
|
||||
const filteringError = new Error("Response was filtered by content policy")
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
|
||||
})
|
||||
|
||||
it("should detect content filtering errors with 'inappropriate' keyword", async () => {
|
||||
const filteringError = new Error("Content deemed inappropriate")
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
|
||||
})
|
||||
|
||||
it("should detect content filtering errors with 'safety' keyword", async () => {
|
||||
const filteringError = new Error("Safety violation detected")
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
|
||||
})
|
||||
|
||||
it("should detect content filtering errors with 'blocked' keyword", async () => {
|
||||
const filteringError = new Error("Request blocked by policy")
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
|
||||
})
|
||||
|
||||
it("should detect content filtering errors in error objects", async () => {
|
||||
const filteringError = {
|
||||
message: "Content filtered",
|
||||
code: "CONTENT_POLICY_VIOLATION",
|
||||
}
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
|
||||
})
|
||||
|
||||
it("should detect content filtering errors in error objects with reason property", async () => {
|
||||
const filteringError = {
|
||||
reason: "Content policy violation",
|
||||
type: "FILTERING_ERROR",
|
||||
}
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
|
||||
})
|
||||
|
||||
it("should not detect content filtering for regular errors", async () => {
|
||||
const regularError = new Error("Network timeout")
|
||||
mockClient.sendRequest.mockRejectedValue(regularError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow("Network timeout")
|
||||
})
|
||||
|
||||
it("should handle cancellation errors correctly", async () => {
|
||||
const cancellationError = new vscode.CancellationError()
|
||||
mockClient.sendRequest.mockRejectedValue(cancellationError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Request cancelled by user/)
|
||||
})
|
||||
|
||||
it("should provide helpful error message for content filtering", async () => {
|
||||
const filteringError = new Error("Response got filtered FIX!!")
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
try {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Error)
|
||||
const errorMessage = (error as Error).message
|
||||
expect(errorMessage).toContain("Response was filtered by VS Code's content policy")
|
||||
expect(errorMessage).toContain("Try rephrasing your request")
|
||||
expect(errorMessage).toContain("Original error: Response got filtered FIX!!")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("Case Insensitive Detection", () => {
|
||||
it("should detect content filtering errors case-insensitively", async () => {
|
||||
const filteringError = new Error("CONTENT POLICY VIOLATION")
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
|
||||
})
|
||||
|
||||
it("should detect content filtering in error name case-insensitively", async () => {
|
||||
const filteringError = new Error("Some error")
|
||||
filteringError.name = "CONTENT_FILTERED_ERROR"
|
||||
mockClient.sendRequest.mockRejectedValue(filteringError)
|
||||
|
||||
const systemPrompt = "You are a helpful assistant"
|
||||
const messages = [{ role: "user" as const, content: "Hello" }]
|
||||
|
||||
await expect(async () => {
|
||||
const generator = handler.createMessage(systemPrompt, messages)
|
||||
for await (const chunk of generator) {
|
||||
// Should not reach here
|
||||
}
|
||||
}).rejects.toThrow(/Response was filtered by VS Code's content policy/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -287,6 +287,60 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
|
|||
}
|
||||
}
|
||||
|
||||
private isContentFilteringError(error: Error): boolean {
|
||||
const message = error.message.toLowerCase()
|
||||
const name = error.name.toLowerCase()
|
||||
|
||||
// Common patterns that indicate content filtering
|
||||
const filteringPatterns = [
|
||||
"filtered",
|
||||
"content policy",
|
||||
"inappropriate",
|
||||
"safety",
|
||||
"blocked",
|
||||
"restricted",
|
||||
"content violation",
|
||||
"policy violation",
|
||||
"harmful content",
|
||||
"unsafe content",
|
||||
]
|
||||
|
||||
return filteringPatterns.some((pattern) => message.includes(pattern) || name.includes(pattern))
|
||||
}
|
||||
|
||||
private isContentFilteringErrorObject(error: any): boolean {
|
||||
if (!error || typeof error !== "object") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check various properties that might indicate content filtering
|
||||
const checkProperties = ["message", "reason", "code", "type", "error", "description"]
|
||||
|
||||
for (const prop of checkProperties) {
|
||||
if (error[prop] && typeof error[prop] === "string") {
|
||||
const value = error[prop].toLowerCase()
|
||||
const filteringPatterns = [
|
||||
"filtered",
|
||||
"content policy",
|
||||
"inappropriate",
|
||||
"safety",
|
||||
"blocked",
|
||||
"restricted",
|
||||
"content violation",
|
||||
"policy violation",
|
||||
"harmful content",
|
||||
"unsafe content",
|
||||
]
|
||||
|
||||
if (filteringPatterns.some((pattern) => value.includes(pattern))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private async getClient(): Promise<vscode.LanguageModelChat> {
|
||||
if (!this.client) {
|
||||
console.debug("Roo Code <Language Model API>: Getting client with options:", {
|
||||
|
|
@ -467,12 +521,33 @@ export class VsCodeLmHandler extends BaseProvider implements SingleCompletionHan
|
|||
name: error.name,
|
||||
})
|
||||
|
||||
// Check for content filtering errors
|
||||
if (this.isContentFilteringError(error)) {
|
||||
throw new Error(
|
||||
"Roo Code <Language Model API>: Response was filtered by VS Code's content policy. " +
|
||||
"This may happen when the model's response contains content that VS Code considers inappropriate. " +
|
||||
"Try rephrasing your request or using a different approach. " +
|
||||
`Original error: ${error.message}`,
|
||||
)
|
||||
}
|
||||
|
||||
// Return original error if it's already an Error instance
|
||||
throw error
|
||||
} else if (typeof error === "object" && error !== null) {
|
||||
// Handle error-like objects
|
||||
const errorDetails = JSON.stringify(error, null, 2)
|
||||
console.error("Roo Code <Language Model API>: Stream error object:", errorDetails)
|
||||
|
||||
// Check if the error object indicates content filtering
|
||||
if (this.isContentFilteringErrorObject(error)) {
|
||||
throw new Error(
|
||||
"Roo Code <Language Model API>: Response was filtered by VS Code's content policy. " +
|
||||
"This may happen when the model's response contains content that VS Code considers inappropriate. " +
|
||||
"Try rephrasing your request or using a different approach. " +
|
||||
`Original error: ${errorDetails}`,
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error(`Roo Code <Language Model API>: Response stream error: ${errorDetails}`)
|
||||
} else {
|
||||
// Fallback for unknown error types
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue