fix(ollama): add logging and diagnostics for debugging Ollama connection issues

- Add logging when models are filtered out due to missing tool support
- Add warning when selected model is not in the tool-capable models list
- Update parseOllamaModel to return filteredReason for better diagnostics
- Log request info (model ID, base URL) when starting Ollama requests

This helps diagnose issues like #11049 where Ollama models appear
unresponsive. The logging output will show:
- Which models are being filtered and why
- When a model may not support native tool calling
- Request details for debugging connectivity issues
This commit is contained in:
Roo Code 2026-01-28 19:47:36 +00:00
parent fe722dad23
commit 9869b06b9e
3 changed files with 63 additions and 25 deletions

View file

@ -15,9 +15,9 @@ describe("Ollama Fetcher", () => {
describe("parseOllamaModel", () => {
it("should correctly parse Ollama model info", () => {
const modelData = ollamaModelsData["qwen3-2to16:latest"]
const parsedModel = parseOllamaModel(modelData)
const { modelInfo } = parseOllamaModel(modelData)
expect(parsedModel).toEqual({
expect(modelInfo).toEqual({
maxTokens: 40960,
contextWindow: 40960,
supportsImages: false,
@ -39,9 +39,9 @@ describe("Ollama Fetcher", () => {
},
}
const parsedModel = parseOllamaModel(modelDataWithNullFamilies as any)
const { modelInfo } = parseOllamaModel(modelDataWithNullFamilies as any)
expect(parsedModel).toEqual({
expect(modelInfo).toEqual({
maxTokens: 40960,
contextWindow: 40960,
supportsImages: false,
@ -54,16 +54,18 @@ describe("Ollama Fetcher", () => {
})
})
it("should return null when capabilities does not include 'tools'", () => {
it("should return null with reason when capabilities does not include 'tools'", () => {
const modelDataWithoutTools = {
...ollamaModelsData["qwen3-2to16:latest"],
capabilities: ["completion"], // No "tools" capability
}
const parsedModel = parseOllamaModel(modelDataWithoutTools as any)
const { modelInfo, filteredReason } = parseOllamaModel(modelDataWithoutTools as any, "test-model")
// Models without tools capability are filtered out (return null)
expect(parsedModel).toBeNull()
expect(modelInfo).toBeNull()
expect(filteredReason).toContain("test-model")
expect(filteredReason).toContain("do not include 'tools'")
})
it("should return model info when capabilities includes 'tools'", () => {
@ -72,22 +74,25 @@ describe("Ollama Fetcher", () => {
capabilities: ["completion", "tools"], // Has "tools" capability
}
const parsedModel = parseOllamaModel(modelDataWithTools as any)
const { modelInfo, filteredReason } = parseOllamaModel(modelDataWithTools as any)
expect(parsedModel).not.toBeNull()
expect(parsedModel!.contextWindow).toBeGreaterThan(0)
expect(modelInfo).not.toBeNull()
expect(modelInfo!.contextWindow).toBeGreaterThan(0)
expect(filteredReason).toBeUndefined()
})
it("should return null when capabilities is undefined (no tool support)", () => {
it("should return null with reason when capabilities is undefined (no tool support)", () => {
const modelDataWithoutCapabilities = {
...ollamaModelsData["qwen3-2to16:latest"],
capabilities: undefined, // No capabilities array
}
const parsedModel = parseOllamaModel(modelDataWithoutCapabilities as any)
const { modelInfo, filteredReason } = parseOllamaModel(modelDataWithoutCapabilities as any, "test-model")
// Models without explicit tools capability are filtered out
expect(parsedModel).toBeNull()
expect(modelInfo).toBeNull()
expect(filteredReason).toContain("test-model")
expect(filteredReason).toContain("no capabilities reported")
})
it("should return null when model has vision but no tools capability", () => {
@ -96,10 +101,10 @@ describe("Ollama Fetcher", () => {
capabilities: ["completion", "vision"],
}
const parsedModel = parseOllamaModel(modelDataWithVision as any)
const { modelInfo } = parseOllamaModel(modelDataWithVision as any)
// No "tools" capability means filtered out
expect(parsedModel).toBeNull()
expect(modelInfo).toBeNull()
})
it("should return model with both vision and tools when both capabilities present", () => {
@ -108,11 +113,11 @@ describe("Ollama Fetcher", () => {
capabilities: ["completion", "vision", "tools"],
}
const parsedModel = parseOllamaModel(modelDataWithBoth as any)
const { modelInfo } = parseOllamaModel(modelDataWithBoth as any)
expect(parsedModel).not.toBeNull()
expect(parsedModel!.supportsImages).toBe(true)
expect(parsedModel!.contextWindow).toBeGreaterThan(0)
expect(modelInfo).not.toBeNull()
expect(modelInfo!.supportsImages).toBe(true)
expect(modelInfo!.contextWindow).toBeGreaterThan(0)
})
})
@ -177,7 +182,7 @@ describe("Ollama Fetcher", () => {
expect(Object.keys(result).length).toBe(1)
expect(result[modelName]).toBeDefined()
const expectedParsedDetails = parseOllamaModel(mockApiShowResponse as any)
const { modelInfo: expectedParsedDetails } = parseOllamaModel(mockApiShowResponse as any)
expect(result[modelName]).toEqual(expectedParsedDetails)
})

View file

@ -37,7 +37,10 @@ type OllamaModelsResponse = z.infer<typeof OllamaModelsResponseSchema>
type OllamaModelInfoResponse = z.infer<typeof OllamaModelInfoResponseSchema>
export const parseOllamaModel = (rawModel: OllamaModelInfoResponse): ModelInfo | null => {
export const parseOllamaModel = (
rawModel: OllamaModelInfoResponse,
modelName?: string,
): { modelInfo: ModelInfo | null; filteredReason?: string } => {
const contextKey = Object.keys(rawModel.model_info).find((k) => k.includes("context_length"))
const contextWindow =
contextKey && typeof rawModel.model_info[contextKey] === "number" ? rawModel.model_info[contextKey] : undefined
@ -45,7 +48,10 @@ export const parseOllamaModel = (rawModel: OllamaModelInfoResponse): ModelInfo |
// Filter out models that don't support tools. Models without tool capability won't work.
const supportsTools = rawModel.capabilities?.includes("tools") ?? false
if (!supportsTools) {
return null
const reason = rawModel.capabilities
? `Model '${modelName || "unknown"}' capabilities (${rawModel.capabilities.join(", ")}) do not include 'tools'`
: `Model '${modelName || "unknown"}' has no capabilities reported (Ollama may need to be updated)`
return { modelInfo: null, filteredReason: reason }
}
const modelInfo: ModelInfo = Object.assign({}, ollamaDefaultModelInfo, {
@ -56,7 +62,7 @@ export const parseOllamaModel = (rawModel: OllamaModelInfoResponse): ModelInfo |
maxTokens: contextWindow || ollamaDefaultModelInfo.contextWindow,
})
return modelInfo
return { modelInfo }
}
export async function getOllamaModels(
@ -84,6 +90,8 @@ export async function getOllamaModels(
let modelInfoPromises = []
if (parsedResponse.success) {
const filteredModels: string[] = []
for (const ollamaModel of parsedResponse.data.models) {
modelInfoPromises.push(
axios
@ -95,16 +103,28 @@ export async function getOllamaModels(
{ headers },
)
.then((ollamaModelInfo) => {
const modelInfo = parseOllamaModel(ollamaModelInfo.data)
const { modelInfo, filteredReason } = parseOllamaModel(
ollamaModelInfo.data,
ollamaModel.name,
)
// Only include models that support native tools
if (modelInfo) {
models[ollamaModel.name] = modelInfo
} else if (filteredReason) {
filteredModels.push(filteredReason)
}
}),
)
}
await Promise.all(modelInfoPromises)
// Log filtered models to help users understand why models aren't appearing
if (filteredModels.length > 0) {
console.warn(
`[Ollama] ${filteredModels.length} model(s) filtered out due to missing tool support:\n${filteredModels.join("\n")}`,
)
}
} else {
console.error(`Error parsing Ollama models response: ${JSON.stringify(parsedResponse.error, null, 2)}`)
}

View file

@ -206,9 +206,22 @@ export class NativeOllamaHandler extends BaseProvider implements SingleCompletio
metadata?: ApiHandlerCreateMessageMetadata,
): ApiStream {
const client = this.ensureClient()
const { id: modelId } = await this.fetchModel()
const { id: modelId, info: modelInfo } = await this.fetchModel()
const useR1Format = modelId.toLowerCase().includes("deepseek-r1")
// Log request info for debugging
const baseUrl = this.options.ollamaBaseUrl || "http://localhost:11434"
console.log(`[Ollama] Starting request to model '${modelId}' at ${baseUrl}`)
// Warn if the model is not in the fetched models list (may indicate missing tool support)
if (!this.models[modelId]) {
console.warn(
`[Ollama] Warning: Model '${modelId}' was not found in the list of tool-capable models. ` +
`This may indicate the model does not support native tool calling. ` +
`Check if your Ollama version reports capabilities by running: ollama show ${modelId}`,
)
}
const ollamaMessages: Message[] = [
{ role: "system", content: systemPrompt },
...convertToOllamaMessages(messages),