diff --git a/src/api/providers/__tests__/nanogpt.spec.ts b/src/api/providers/__tests__/nanogpt.spec.ts index 6ac9a17d61..f8d6e61241 100644 --- a/src/api/providers/__tests__/nanogpt.spec.ts +++ b/src/api/providers/__tests__/nanogpt.spec.ts @@ -162,6 +162,78 @@ describe("NanoGptHandler", () => { expect(mockCreate.mock.calls[0][0]).not.toHaveProperty("max_completion_tokens") }) + it("keeps Muse Spark tool-result history contiguous across turns", async () => { + const modelId = "meta/muse-spark-1.2-contributor" + vi.mocked(getModels).mockResolvedValue({ + [modelId]: { + maxTokens: 65_536, + contextWindow: 1_000_000, + supportsPromptCache: false, + }, + }) + const tools: OpenAI.Chat.ChatCompletionTool[] = [ + { type: "function", function: { name: "read_file", parameters: { type: "object" } } }, + ] + const toolHistory: Anthropic.Messages.MessageParam[] = [ + { + role: "assistant", + content: [{ type: "tool_use", id: "call_1", name: "read_file", input: { path: "first.txt" } }], + }, + { + role: "user", + content: [ + { type: "tool_result", tool_use_id: "call_1", content: "first result" }, + { type: "text", text: "first context" }, + ], + }, + { + role: "assistant", + content: [{ type: "tool_use", id: "call_2", name: "read_file", input: { path: "second.txt" } }], + }, + { + role: "user", + content: [ + { type: "tool_result", tool_use_id: "call_2", content: "second result" }, + { type: "text", text: "second context" }, + ], + }, + ] + + await collectStream( + new NanoGptHandler({ nanoGptModelId: modelId }).createMessage("sys", toolHistory, { + taskId: "task", + tools, + tool_choice: "auto", + parallelToolCalls: true, + }), + ) + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + model: modelId, + messages: [ + { role: "system", content: "sys" }, + expect.objectContaining({ role: "assistant", tool_calls: [expect.anything()] }), + { + role: "tool", + tool_call_id: "call_1", + content: "first result\n\nfirst context", + }, + expect.objectContaining({ role: "assistant", tool_calls: [expect.anything()] }), + { + role: "tool", + tool_call_id: "call_2", + content: "second result\n\nsecond context", + }, + ], + tools: [expect.objectContaining({ function: expect.objectContaining({ name: "read_file" }) })], + tool_choice: "auto", + parallel_tool_calls: true, + }), + expect.anything(), + ) + }) + it("omits temperature when it was not explicitly configured", async () => { await collectStream(new NanoGptHandler({ nanoGptModelId: "model:thinking" }).createMessage("sys", messages)) expect(mockCreate.mock.calls[0][0]).not.toHaveProperty("temperature") diff --git a/src/api/providers/nanogpt.ts b/src/api/providers/nanogpt.ts index 38fb54c384..7b0c7930d3 100644 --- a/src/api/providers/nanogpt.ts +++ b/src/api/providers/nanogpt.ts @@ -27,6 +27,8 @@ type NanoGptUsage = OpenAI.CompletionUsage & { type NanoGptCachingRequest = { caching?: true } +const NANO_GPT_MERGED_TOOL_RESULT_MODELS = new Set(["meta/muse-spark-1.2-contributor"]) + const OPENAI_REASONING_EFFORTS = ["low", "medium", "high"] as const type OpenAiReasoningEffort = (typeof OPENAI_REASONING_EFFORTS)[number] @@ -93,7 +95,12 @@ export class NanoGptHandler extends RouterProvider implements SingleCompletionHa const { id: canonicalModelId, info } = await this.fetchModel() const body: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming & NanoGptCachingRequest = { model: this.getRequestModelId(canonicalModelId), - messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], + messages: [ + { role: "system", content: systemPrompt }, + ...convertToOpenAiMessages(messages, { + mergeToolResultText: NANO_GPT_MERGED_TOOL_RESULT_MODELS.has(canonicalModelId), + }), + ], stream: true, stream_options: { include_usage: true }, max_tokens: info.maxTokens ?? undefined,