[inference] handle toolCall indices not starting at 0 (#205954)

## Summary

Turns out, claude can in some situations (when returning both text and
toolcall in a single message) starts their toolcall index at `1` instead
of `0`, which introducing null values in the concatenated messages.

This fixes it, by removing null values from the tool calls when merging
the chunks.

Also remove the SKA codeowner override for the inference plugin to get
back the shared ownership
This commit is contained in:
Pierre Gayvallet 2025-01-09 08:00:31 +01:00 committed by GitHub
parent cbcb24e036
commit 8eec8065bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 89 additions and 1 deletions

1
.github/CODEOWNERS vendored
View file

@ -3258,7 +3258,6 @@ x-pack/platform/plugins/shared/fields_metadata @elastic/obs-ux-logs-team
x-pack/platform/plugins/shared/fleet @elastic/fleet
x-pack/platform/plugins/shared/global_search @elastic/appex-sharedux
x-pack/platform/plugins/shared/index_management @elastic/kibana-management
x-pack/platform/plugins/shared/inference @elastic/appex-ai-infra
x-pack/platform/plugins/shared/ingest_pipelines @elastic/kibana-management
x-pack/platform/plugins/shared/integration_assistant @elastic/security-scalability
x-pack/platform/plugins/shared/lens @elastic/kibana-visualizations

View file

@ -141,6 +141,92 @@ describe('chunksIntoMessage', () => {
});
});
it('parses tool calls even when the index does not start at 0', async () => {
const message = await lastValueFrom(
chunksIntoMessage({
toolOptions: {
toolChoice: ToolChoiceType.auto,
tools: {
myFunction: {
description: 'myFunction',
schema: {
type: 'object',
properties: {
foo: {
type: 'string',
const: 'bar',
},
},
},
},
},
},
logger,
})(
fromEvents(
{
content: '',
type: ChatCompletionEventType.ChatCompletionChunk,
tool_calls: [
{
function: {
name: 'myFunction',
arguments: '',
},
index: 1,
toolCallId: '0',
},
],
},
{
content: '',
type: ChatCompletionEventType.ChatCompletionChunk,
tool_calls: [
{
function: {
name: '',
arguments: '{',
},
index: 1,
toolCallId: '0',
},
],
},
{
content: '',
type: ChatCompletionEventType.ChatCompletionChunk,
tool_calls: [
{
function: {
name: '',
arguments: '"foo": "bar" }',
},
index: 1,
toolCallId: '1',
},
],
}
)
)
);
expect(message).toEqual({
content: '',
toolCalls: [
{
function: {
name: 'myFunction',
arguments: {
foo: 'bar',
},
},
toolCallId: '001',
},
],
type: ChatCompletionEventType.ChatCompletionMessage,
});
});
it('validates tool calls', async () => {
async function getMessage() {
return await lastValueFrom(

View file

@ -67,6 +67,9 @@ export function chunksIntoMessage<TToolOptions extends ToolOptions>({
{ content: '', tool_calls: [] as UnvalidatedToolCall[] }
);
// some models (Claude not to name it) can have their toolCall index not start at 0, so we remove the null elements
concatenatedChunk.tool_calls = concatenatedChunk.tool_calls.filter((call) => !!call);
logger.debug(() => `Received completed message: ${JSON.stringify(concatenatedChunk)}`);
const validatedToolCalls = validateToolCalls<TToolOptions>({