diff --git a/core/llm/llms/Ollama.test.ts b/core/llm/llms/Ollama.test.ts index 78d69dd157a..501915ea54c 100644 --- a/core/llm/llms/Ollama.test.ts +++ b/core/llm/llms/Ollama.test.ts @@ -127,6 +127,68 @@ describe("Ollama", () => { }); }); + describe("_streamChat", () => { + const tool = { + type: "function", + function: { + name: "read_file", + description: "Read a file", + parameters: { + type: "object", + required: ["filepath"], + properties: { filepath: { type: "string" } }, + }, + }, + }; + + async function requestBody(messages: ChatMessage[]) { + const ollama = createOllama(); + (ollama as any).ensureModelInfo = jest.fn(); + (ollama as any).getEndpoint = jest.fn(() => "http://localhost/api/chat"); + (ollama as any)._getModel = jest.fn(() => "test-model"); + (ollama.fetch as jest.Mock).mockResolvedValue({ + json: async () => ({ + message: { role: "assistant", content: "done" }, + }), + }); + + const stream = (ollama as any)._streamChat( + messages, + new AbortController().signal, + { stream: false, tools: [tool] }, + ); + for await (const _ of stream) { + // Drain the response so the request completes. + } + + const [, init] = (ollama.fetch as jest.Mock).mock.calls[0]; + return JSON.parse(init.body); + } + + it("should attach tools after a tool result for multi-step agent loops", async () => { + const body = await requestBody([ + { role: "user", content: "Read file A, then file B" }, + { + role: "assistant", + content: "", + toolCalls: [ + { + id: "tc_1", + type: "function", + function: { + name: "read_file", + arguments: '{"filepath":"fileA"}', + }, + }, + ], + }, + { role: "tool", content: "file A", toolCallId: "tc_1" }, + ]); + + expect(body.tools).toEqual([tool]); + }); + }); + describe("_reorderMessagesForToolCompat", () => { let ollama: Ollama; diff --git a/core/llm/llms/Ollama.ts b/core/llm/llms/Ollama.ts index 4bcd9fb1e0f..a2d78c47f04 100644 --- a/core/llm/llms/Ollama.ts +++ b/core/llm/llms/Ollama.ts @@ -511,7 +511,7 @@ class Ollama extends BaseLLM implements ModelInstaller { stream: options.stream, // format: options.format, // Not currently in base completion options }; - if (options.tools?.length && ollamaMessages.at(-1)?.role === "user") { + if (options.tools?.length) { chatOptions.tools = options.tools.map((tool) => ({ type: "function", function: {