Skip to content

Commit 16cce23

Browse files
committed
fix ollama chat dropping tool data
1 parent f6fcb2b commit 16cce23

3 files changed

Lines changed: 135 additions & 3 deletions

File tree

packages/kernel-language-model-service/src/ollama/base.test.ts

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { GenerateResponse, ListResponse } from 'ollama';
1+
import type { ChatResponse, GenerateResponse, ListResponse } from 'ollama';
22
import { describe, it, expect, vi, beforeEach } from 'vitest';
33

44
import type { OllamaClient, OllamaModelOptions } from './types.ts';
@@ -15,6 +15,7 @@ describe('OllamaBaseService', () => {
1515
mockClient = {
1616
list: vi.fn(),
1717
generate: vi.fn(),
18+
chat: vi.fn(),
1819
};
1920

2021
mockMakeClient = vi.fn().mockResolvedValue(mockClient);
@@ -134,6 +135,111 @@ describe('OllamaBaseService', () => {
134135
});
135136
});
136137

138+
describe('chat', () => {
139+
const makeChatResponse = (
140+
overrides: Partial<ChatResponse> = {},
141+
): ChatResponse =>
142+
({
143+
model: 'llama3',
144+
message: { role: 'assistant', content: 'hello' },
145+
done: true,
146+
done_reason: 'stop',
147+
prompt_eval_count: 5,
148+
eval_count: 3,
149+
...overrides,
150+
}) as ChatResponse;
151+
152+
it('forwards tools to the Ollama client', async () => {
153+
vi.mocked(mockClient.chat).mockResolvedValue(makeChatResponse());
154+
155+
await service.chat({
156+
model: 'llama3',
157+
messages: [{ role: 'user', content: 'hi' }],
158+
tools: [
159+
{
160+
type: 'function',
161+
function: { name: 'get_time', description: 'Returns the time' },
162+
},
163+
],
164+
});
165+
166+
expect(mockClient.chat).toHaveBeenCalledWith(
167+
expect.objectContaining({
168+
tools: [
169+
{
170+
type: 'function',
171+
function: { name: 'get_time', description: 'Returns the time' },
172+
},
173+
],
174+
}),
175+
);
176+
});
177+
178+
it('forwards message tool_calls with parsed arguments', async () => {
179+
vi.mocked(mockClient.chat).mockResolvedValue(makeChatResponse());
180+
181+
await service.chat({
182+
model: 'llama3',
183+
messages: [
184+
{
185+
role: 'assistant',
186+
content: null,
187+
tool_calls: [
188+
{
189+
id: 'tc-1',
190+
type: 'function',
191+
function: { name: 'get_time', arguments: '{"tz":"UTC"}' },
192+
},
193+
],
194+
},
195+
],
196+
});
197+
198+
expect(mockClient.chat).toHaveBeenCalledWith(
199+
expect.objectContaining({
200+
messages: [
201+
expect.objectContaining({
202+
tool_calls: [
203+
{ function: { name: 'get_time', arguments: { tz: 'UTC' } } },
204+
],
205+
}),
206+
],
207+
}),
208+
);
209+
});
210+
211+
it('maps response tool_calls back with stringified arguments', async () => {
212+
vi.mocked(mockClient.chat).mockResolvedValue(
213+
makeChatResponse({
214+
message: {
215+
role: 'assistant',
216+
content: '',
217+
tool_calls: [
218+
{ function: { name: 'get_time', arguments: { tz: 'UTC' } } },
219+
],
220+
},
221+
}),
222+
);
223+
224+
const result = await service.chat({
225+
model: 'llama3',
226+
messages: [{ role: 'user', content: 'what time is it?' }],
227+
});
228+
229+
expect(result.choices[0]?.message).toStrictEqual({
230+
role: 'assistant',
231+
content: '',
232+
tool_calls: [
233+
{
234+
id: 'tool-0',
235+
type: 'function',
236+
function: { name: 'get_time', arguments: '{"tz":"UTC"}' },
237+
},
238+
],
239+
});
240+
});
241+
});
242+
137243
describe('makeInstance', () => {
138244
it('should create instance with model', async () => {
139245
const config: InstanceConfig<OllamaModelOptions> = {

packages/kernel-language-model-service/src/ollama/base.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
OllamaModel,
2020
OllamaClient,
2121
OllamaModelOptions,
22+
OllamaTool,
2223
} from './types.ts';
2324

2425
/**
@@ -73,18 +74,30 @@ export class OllamaBaseService<Ollama extends OllamaClient>
7374
* @returns A hardened chat result.
7475
*/
7576
async chat(params: ChatParams): Promise<ChatResult> {
76-
const { model, messages, temperature, seed, stop } = params;
77+
const { model, messages, tools, temperature, seed, stop } = params;
7778
const ollama = await this.#makeClient();
7879
let stopArr: string[] | undefined;
7980
if (stop !== undefined) {
8081
stopArr = Array.isArray(stop) ? stop : [stop];
8182
}
8283
const response = await ollama.chat({
8384
model,
84-
messages: messages.map(({ role, content }) => ({
85+
// eslint-disable-next-line camelcase
86+
messages: messages.map(({ role, content, tool_calls }) => ({
8587
role,
8688
content: content ?? '',
89+
// eslint-disable-next-line camelcase
90+
...(tool_calls && {
91+
// eslint-disable-next-line camelcase
92+
tool_calls: tool_calls.map(({ function: fn }) => ({
93+
function: {
94+
name: fn.name,
95+
arguments: JSON.parse(fn.arguments) as Record<string, unknown>,
96+
},
97+
})),
98+
}),
8799
})),
100+
...(tools && { tools: tools as OllamaTool[] }),
88101
stream: false,
89102
options: ifDefined({
90103
temperature,
@@ -96,6 +109,7 @@ export class OllamaBaseService<Ollama extends OllamaClient>
96109
});
97110
const promptTokens = response.prompt_eval_count ?? 0;
98111
const completionTokens = response.eval_count ?? 0;
112+
const { tool_calls: responseToolCalls } = response.message;
99113
return harden({
100114
id: 'ollama-chat',
101115
model: response.model,
@@ -104,6 +118,16 @@ export class OllamaBaseService<Ollama extends OllamaClient>
104118
message: {
105119
role: response.message.role as ChatRole,
106120
content: response.message.content,
121+
...(responseToolCalls && {
122+
tool_calls: responseToolCalls.map((tc, index) => ({
123+
id: `tool-${index}`,
124+
type: 'function' as const,
125+
function: {
126+
name: tc.function.name,
127+
arguments: JSON.stringify(tc.function.arguments),
128+
},
129+
})),
130+
}),
107131
},
108132
index: 0,
109133
finish_reason: response.done_reason ?? 'stop',

packages/kernel-language-model-service/src/ollama/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
Config,
99
ChatRequest,
1010
ChatResponse,
11+
Tool as OllamaTool,
1112
} from 'ollama';
1213

1314
import type { LanguageModel } from '../types.ts';
@@ -30,6 +31,7 @@ export type {
3031
GenerateRequest,
3132
GenerateResponse,
3233
OllamaClient,
34+
OllamaTool,
3335
ChatRequest,
3436
ChatResponse,
3537
};

0 commit comments

Comments
 (0)