This commit is contained in:
Bastian Wagner
2026-06-12 14:23:55 +02:00
parent 26e0ba5caf
commit d75c0ecc68
2 changed files with 79 additions and 2 deletions

View File

@@ -128,6 +128,58 @@ describe('AssistantService', () => {
);
});
it('extracts the final assistant message from multi-completion responses', async () => {
const providerResponse = {
id: 'chatcmpl-test',
object: 'chat.multi_completion',
choices: [
{
messages: [
{
role: 'assistant',
index: 0,
content: '',
tool_calls: [
{
id: 'call-1',
type: 'function',
function: {
name: 'listify_list_existing_lists',
arguments: '{"includeItems": false}',
},
},
],
},
{
role: 'tool',
index: 1,
content: [{ type: 'text', text: '{"lists":[]}' }],
},
{
role: 'assistant',
index: 2,
content: 'Hier sind deine bestehenden Listen.',
},
],
finish_reason: 'stop',
},
],
};
mockMistralResponse(providerResponse);
const result = await service.chat('user-1', {
messages: [{ role: 'user', content: 'Welche Listen habe ich?' }],
});
expect(result.message.content).toBe('Hier sind deine bestehenden Listen.');
expect(chatLogsRepository.save).toHaveBeenCalledWith(
expect.objectContaining({
responsePayload: providerResponse,
assistantContent: 'Hier sind deine bestehenden Listen.',
}),
);
});
it('fails clearly when the api key is missing', async () => {
delete process.env.MISTRAL_API_KEY;

View File

@@ -18,6 +18,10 @@ interface MistralAgentCompletionResponse {
message?: {
content?: string | null;
};
messages?: Array<{
role?: string;
content?: string | null | unknown[];
}>;
}>;
}
@@ -36,7 +40,7 @@ export class AssistantService {
): Promise<AssistantChatResponse> {
const messages = this.normalizeMessages(request.messages);
const response = await this.callMistralAgent(userId, messages);
const content = response.choices?.[0]?.message?.content?.trim();
const content = this.extractAssistantContent(response);
if (!content) {
throw new ServiceUnavailableException('Mistral response was empty.');
@@ -149,7 +153,28 @@ export class AssistantService {
}
const response = responsePayload as MistralAgentCompletionResponse;
return response.choices?.[0]?.message?.content?.trim() ?? null;
const directContent = response.choices?.[0]?.message?.content?.trim();
if (directContent) {
return directContent;
}
for (const choice of response.choices ?? []) {
const assistantMessages = (choice.messages ?? [])
.filter((message) => message.role === 'assistant')
.reverse();
for (const message of assistantMessages) {
const content =
typeof message.content === 'string' ? message.content.trim() : '';
if (content) {
return content;
}
}
}
return null;
}
private async recordChatLog(input: {