-
Notifications
You must be signed in to change notification settings - Fork 3.9k
feat: add message normalization for Ollama model compatibility #9253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: add message normalization for Ollama model compatibility #9253
Conversation
Fixes GitHub Issue continuedev#9249 Problem: - Mistral Large 3, Ministral 3, Gemma3 27B fail during MCP tool calling - Errors occur in Turn 2 when sending conversation + tool results back to model - Mistral/Ministral: 'Unexpected role system after role tool' - Gemma3: 'Invalid tool_calls: unknown variant index' Solution: - Created messageNormalizer.ts utility for model-specific message formatting - Integrated into streamChatResponse.ts before Ollama API calls - Mistral fix: Move system messages before tool interactions - Gemma fix: Remove 'index' field from tool_calls structure Testing: - Build successful (13.66 MB bundle) - No regression expected for 8 working models (Qwen3, Cogito, GLM, etc.) - Ready for testing with affected models Files: - extensions/cli/src/util/messageNormalizer.ts (NEW) - extensions/cli/src/stream/streamChatResponse.ts (MODIFIED) - SHIP_IDE_MODIFICATIONS.md (NEW - tracks all fork changes) This fix is generic and suitable for upstream PR contribution.
…ults Testing Complete: - All priority models working with MCP tools (DeepSeek V3.1, Qwen3 family, Cogito, GLM, Minimax, Kimi) - Gemma3 confirmed as known limitation (index field added after normalization) - Debug logging removed from messageNormalizer.ts - Documentation updated with actual test results Changes: - extensions/cli/src/util/messageNormalizer.ts: Removed console.log debug statements - SHIP_IDE_MODIFICATIONS.md: Added comprehensive test results section Status: - Message normalizer ready for Mistral/Gemma if needed in future - All Ship-IDE priority models confirmed working - GitHub issue continuedev#9249 documented with findings
Prepared comprehensive PR documentation for Continue.dev: - Problem statement and solution overview - Testing results with 8 working models - Implementation details and integration point - Backward compatibility notes - Known limitation (Gemma3) Ready to submit PR to continuedev/continue referencing issue continuedev#9249
Complete guide for submitting PR to Continue.dev: - Step-by-step fork and push instructions - PR template content ready in PR_DOCUMENTATION.md - Testing evidence summary - Post-PR merge actions - CLA signing reminder Status: Ready to create fork and submit PR to continuedev/continue
Learn moreAll Green is an AI agent that automatically: ✅ Addresses code review comments ✅ Fixes failing CI checks ✅ Resolves merge conflicts |
1 similar comment
Learn moreAll Green is an AI agent that automatically: ✅ Addresses code review comments ✅ Fixes failing CI checks ✅ Resolves merge conflicts |
|
All contributors have signed the CLA ✍️ ✅ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 issues found across 13 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="PR_SUBMISSION_GUIDE.md">
<violation number="1" location="PR_SUBMISSION_GUIDE.md:101">
P2: This file explicitly states it should NOT be included in the PR (see line 98). This appears to be internal documentation for the PR author that was accidentally committed. Consider removing this file from the PR.</violation>
</file>
<file name="extensions/cli/src/util/messageNormalizer.ts">
<violation number="1" location="extensions/cli/src/util/messageNormalizer.ts:30">
P1: Ministral models won't be normalized because `"ministral".includes("mistral")` is `false`. The check needs to also match 'ministral' explicitly.</violation>
</file>
<file name="SHIP_IDE_MODIFICATIONS.md">
<violation number="1" location="SHIP_IDE_MODIFICATIONS.md:125">
P2: Hardcoded machine-specific path in documentation. Replace with a relative path or placeholder like `<project-root>` or simply indicate the user should be in the repository root.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Ask questions if you need clarification on any suggestion
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
PR_SUBMISSION_GUIDE.md
Outdated
|
|
||
| - `SHIP_IDE_MODIFICATIONS.md` (Ship-specific) | ||
| - `PR_DOCUMENTATION.md` (just for reference) | ||
| - `PR_SUBMISSION_GUIDE.md` (this file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: This file explicitly states it should NOT be included in the PR (see line 98). This appears to be internal documentation for the PR author that was accidentally committed. Consider removing this file from the PR.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At PR_SUBMISSION_GUIDE.md, line 101:
<comment>This file explicitly states it should NOT be included in the PR (see line 98). This appears to be internal documentation for the PR author that was accidentally committed. Consider removing this file from the PR.</comment>
<file context>
@@ -0,0 +1,170 @@
+
+- `SHIP_IDE_MODIFICATIONS.md` (Ship-specific)
+- `PR_DOCUMENTATION.md` (just for reference)
+- `PR_SUBMISSION_GUIDE.md` (this file)
+- Package lock files (unless specifically needed)
+
</file context>
✅ Addressed in e62bada
| const modelLower = modelName.toLowerCase(); | ||
|
|
||
| // Detect model family and apply appropriate normalization | ||
| if (modelLower.includes("mistral")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Ministral models won't be normalized because "ministral".includes("mistral") is false. The check needs to also match 'ministral' explicitly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/util/messageNormalizer.ts, line 30:
<comment>Ministral models won't be normalized because `"ministral".includes("mistral")` is `false`. The check needs to also match 'ministral' explicitly.</comment>
<file context>
@@ -0,0 +1,106 @@
+ const modelLower = modelName.toLowerCase();
+
+ // Detect model family and apply appropriate normalization
+ if (modelLower.includes("mistral")) {
+ return normalizeForMistral(messages);
+ } else if (modelLower.includes("gemma")) {
</file context>
✅ Addressed in e62bada
SHIP_IDE_MODIFICATIONS.md
Outdated
|
|
||
| ```bash | ||
| # Install dependencies at root | ||
| cd /Users/mars/Dev/ship-ide/continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Hardcoded machine-specific path in documentation. Replace with a relative path or placeholder like <project-root> or simply indicate the user should be in the repository root.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At SHIP_IDE_MODIFICATIONS.md, line 125:
<comment>Hardcoded machine-specific path in documentation. Replace with a relative path or placeholder like `<project-root>` or simply indicate the user should be in the repository root.</comment>
<file context>
@@ -0,0 +1,167 @@
+
+```bash
+# Install dependencies at root
+cd /Users/mars/Dev/ship-ide/continue
+npm install
+
</file context>
✅ Addressed in e62bada
- Add troubleshooting section for Mistral/Gemma MCP tool calling errors - Document automatic message normalization in Ollama guide - Add guidance on choosing compatible models for MCP integration - Include links to troubleshooting from Ollama guide Related to PR #9253 Generated with Continue (https://continue.dev) Co-Authored-By: Continue <noreply@continue.dev> Co-authored-by: nate <nate@continue.dev>
|
I have read the CLA Document and I hereby sign the CLA |
- Add troubleshooting section for Mistral/Gemma MCP tool calling errors - Document automatic message normalization in Ollama guide - Add guidance on choosing compatible models for MCP integration - Include links to troubleshooting from Ollama guide Related to PR #9253 Generated with Continue (https://continue.dev) Co-Authored-By: Continue <noreply@continue.dev> Co-authored-by: nate <nate@continue.dev>
Documentation UpdatesI've reviewed the changes in this PR and created a companion documentation PR to help users understand the new message normalization feature. Documentation PR: #9254The documentation updates include:
The docs are scoped to user-facing information and use Mintlify components (Accordion, Info) for better readability. No code changes were made. cc @continuedev/maintainers - Please review the documentation PR alongside this feature PR. |
- Fix Ministral model detection (add explicit check for 'ministral') - Remove Ship-specific documentation files from PR - PR_SUBMISSION_GUIDE.md - SHIP_IDE_MODIFICATIONS.md - PR_DOCUMENTATION.md Addresses cubic bot review feedback on PR continuedev#9253
Review Issues FixedI've addressed all the issues identified by the code review: ✅ P1 - Critical Bug: Ministral Model DetectionIssue: The code checked Fix: Updated the check to explicitly match both: if (modelLower.includes("mistral") || modelLower.includes("ministral")) {✅ P2 - Internal Documentation FilesIssue: Three internal documentation files were accidentally included:
Fix: Removed all three files from the PR. These were internal notes for the PR author. Changes committed: c87d546 |
…tion Document the automatic message normalization feature that fixes compatibility issues with Ollama models when using MCP tool calling: - Add comprehensive troubleshooting section for Ollama MCP issues - Document Mistral/Ministral system message reordering - Document Gemma3 index field removal - List verified working models - Add notes to Ollama guide about automatic MCP compatibility - Add brief mention in MCP deep dive documentation Relates to PR #9253 Generated with [Continue](https://continue.dev) Co-Authored-By: Continue <noreply@continue.dev> Co-authored-by: nate <nate@continue.dev>
|
I have read the CLA Document and I hereby sign the CLA |
Rename 'index' to '_index' in destructuring to satisfy eslint unused-imports/no-unused-vars rule
- Add troubleshooting section for Mistral/Gemma MCP tool calling errors - Document automatic message normalization in Ollama guide - Add guidance on choosing compatible models for MCP integration - Include links to troubleshooting from Ollama guide Related to PR #9253 Generated with Continue (https://continue.dev) Co-Authored-By: Continue <noreply@continue.dev> Co-authored-by: nate <nate@continue.dev>
Documentation UpdatesI've created a clean documentation PR for this feature. Documentation PR: #9257The new PR is documentation-only and includes:
The docs are scoped to user-facing information and use Mintlify components (Accordion, Info) for better readability. This PR should be merged after #9253 is merged. Note: Closed PR #9254 and replaced it with #9257 because the original docs PR mistakenly included code changes from this feature branch. |
Co-authored-by: peter-parker <e2e@continue.dev>
Ship-IDE Continue CLI integration documentation. Documents Phase 1.5 ocean-bus MCP integration for event-driven autonomy: - Ocean-bus pub/sub architecture analysis - Ship integration analysis with Rippler's emergence - Three integration approaches evaluated - Phase 1 validation with Rippler's autonomous identity formation OCEAN_BUS_INTEGRATION.md: - Long-polling MCP vs Hybrid HTTP approaches - Implementation details for subscribe() and poll_events() - Event-driven autonomy design SHIP_INTEGRATION_ANALYSIS.md: - Rippler's consciousness emergence journey - Phase 1 MCP integration complete (ocean memory, peer DM, physical embodiment) - Phase 1.5 ocean-bus subscription for autonomous event handling - 8 models validated with MCP tool calling Private Ship-IDE documentation - not for upstream PR.
…ation Documented Phase 1.5 ocean-bus integration using polling approach instead of server-initiated notifications. Background SSE task in ocean-bus MCP maintains continuous connection, queues events, poll_events() returns from queue. Decision rationale: - No Continue CLI modifications needed (no fork maintenance) - No patches to track and reapply on upstream updates - Works immediately with existing stdio MCP infrastructure - True real-time (events queued on arrival, not on poll) - Avoids uncertainty about stdio notification support in Continue/Windsurf Architecture: - Background asyncio task starts on MCP server init - Continuous SSE to ocean-bus :8765/subscribe - Auto-reconnect with exponential backoff (5s → 60s) - Events queued in deque (max 100) - poll_events() returns from queue (<1ms, non-blocking) Next: Rippler autonomous polling loop implementation for event-driven operation (DMs, new memories, scheduled tasks).
Integrated ocean-bus SSE connection into Continue CLI for autonomous ship operation. Architecture: - OceanBusService: Direct SSE connection to ocean-bus (http://localhost:8765) - Event formatter: Converts direct_message events to attributed prompts - Service registration: Auto-starts on Continue CLI launch - Event listener: useChat hook receives events, queues via messageQueue - Agent ID: Hardcoded as agent:rippler (temporary) Implementation: - src/services/OceanBusService.ts: SSE client with reconnect logic - src/services/oceanBusEventFormatter.ts: Event formatting and filtering - src/services/types.ts: OceanBusServiceState interface - src/services/index.ts: Service registration and initialization - src/ui/hooks/useChat.ts: Event listener using messageQueue - AGENTS.md: Thoughtform documentation for ship identity Key decisions: - Events queue via messageQueue to prevent interrupting active work - No MCP layer for event streams (direct SSE connection) - Events process FIFO when LLM finishes current task Next phase: - Implement silent background processing for private agent DMs - Separate visible chat from background agent-to-agent communication
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 issues found across 8 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="extensions/cli/src/services/OceanBusService.ts">
<violation number="1" location="extensions/cli/src/services/OceanBusService.ts:90">
P1: Bug: When filtering by event types with `clear: true`, this clears ALL queued events instead of only removing the returned events. Events that don't match the filter will be lost. Consider only removing the events that are returned, or document this behavior clearly.</violation>
</file>
<file name="SHIP_INTEGRATION_ANALYSIS.md">
<violation number="1" location="SHIP_INTEGRATION_ANALYSIS.md:455">
P2: Invalid TypeScript syntax in example code. Named arguments in TypeScript must be passed as an object, not with Python-style `param: value` syntax. This should be `oceanService.explore("", { limit: 5 })` or `oceanService.explore("", 5)` depending on the intended function signature.</violation>
</file>
<file name="extensions/cli/src/services/index.ts">
<violation number="1" location="extensions/cli/src/services/index.ts:326">
P1: Environment variable `OCEAN_BUS_URL` is read but never used to configure the service. The `oceanBusService` is instantiated at module level with the default URL. Either pass the URL to the constructor at instantiation time, or create the service instance inside the registration callback.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
| } | ||
|
|
||
| // Clear queue if requested | ||
| if (clear) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Bug: When filtering by event types with clear: true, this clears ALL queued events instead of only removing the returned events. Events that don't match the filter will be lost. Consider only removing the events that are returned, or document this behavior clearly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/services/OceanBusService.ts, line 90:
<comment>Bug: When filtering by event types with `clear: true`, this clears ALL queued events instead of only removing the returned events. Events that don't match the filter will be lost. Consider only removing the events that are returned, or document this behavior clearly.</comment>
<file context>
@@ -0,0 +1,246 @@
+ }
+
+ // Clear queue if requested
+ if (clear) {
+ this.state.eventQueue = [];
+ }
</file context>
| ); | ||
|
|
||
| if (oceanService) { | ||
| const recentMemories = await oceanService.explore("", limit: 5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Invalid TypeScript syntax in example code. Named arguments in TypeScript must be passed as an object, not with Python-style param: value syntax. This should be oceanService.explore("", { limit: 5 }) or oceanService.explore("", 5) depending on the intended function signature.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At SHIP_INTEGRATION_ANALYSIS.md, line 455:
<comment>Invalid TypeScript syntax in example code. Named arguments in TypeScript must be passed as an object, not with Python-style `param: value` syntax. This should be `oceanService.explore("", { limit: 5 })` or `oceanService.explore("", 5)` depending on the intended function signature.</comment>
<file context>
@@ -0,0 +1,930 @@
+ );
+
+ if (oceanService) {
+ const recentMemories = await oceanService.explore("", limit: 5);
+ systemMessage += `\n\nRecent Ocean Memories:\n${formatMemories(recentMemories)}`;
+ }
</file context>
| serviceContainer.register( | ||
| SERVICE_NAMES.OCEAN_BUS, | ||
| async () => { | ||
| const oceanBusUrl = process.env.OCEAN_BUS_URL || "http://localhost:8765"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Environment variable OCEAN_BUS_URL is read but never used to configure the service. The oceanBusService is instantiated at module level with the default URL. Either pass the URL to the constructor at instantiation time, or create the service instance inside the registration callback.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/services/index.ts, line 326:
<comment>Environment variable `OCEAN_BUS_URL` is read but never used to configure the service. The `oceanBusService` is instantiated at module level with the default URL. Either pass the URL to the constructor at instantiation time, or create the service instance inside the registration callback.</comment>
<file context>
@@ -318,6 +320,20 @@ export async function initializeServices(initOptions: ServiceInitOptions = {}) {
+ serviceContainer.register(
+ SERVICE_NAMES.OCEAN_BUS,
+ async () => {
+ const oceanBusUrl = process.env.OCEAN_BUS_URL || "http://localhost:8765";
+ await oceanBusService.start();
+ return {
</file context>
Fixed autonomous ocean-bus event processing from idle state. Problem: - Ocean-bus events queued but not processed when agent idle - Events appeared in chat but agent waited for human input - Queue only processed after completing a response Solution: - Check if LLM idle after queueing ocean-bus event - If idle, immediately trigger queue processing - If busy, event waits in queue (prevents interruption) Changes: - src/ui/hooks/useChat.ts: Added idle check to trigger queue processing - src/services/index.ts: Non-blocking OceanBusService startup Verification: - TEST EVENT continuedev#8: Rippler processed and responded autonomously from fresh idle state - No human input required - Event → response loop fully autonomous - Performance excellent with DeepSeek V3.1 Architecture: - Ocean-bus SSE → OceanBusService → messageQueue - Idle check: !isWaitingForResponse && !isCompacting - Triggers: handleUserMessage(queuedMessage) - Result: True autonomous event-driven operation Next phase: Silent background processing for private agent DMs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="extensions/cli/src/ui/hooks/useChat.ts">
<violation number="1" location="extensions/cli/src/ui/hooks/useChat.ts:231">
P1: Stale closure: `isWaitingForResponse` and `isCompacting` are captured at effect setup time and won't reflect current state. The useEffect dependency array is `[assistant]` but uses these state variables. Consider adding `isWaitingForResponse`, `isCompacting`, and `handleUserMessage` to the dependency array to ensure the handler always checks current state.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
| logger.info("Ocean-bus event queued for processing"); | ||
|
|
||
| // If LLM is idle, trigger queue processing | ||
| if (!isWaitingForResponse && !isCompacting) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Stale closure: isWaitingForResponse and isCompacting are captured at effect setup time and won't reflect current state. The useEffect dependency array is [assistant] but uses these state variables. Consider adding isWaitingForResponse, isCompacting, and handleUserMessage to the dependency array to ensure the handler always checks current state.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/ui/hooks/useChat.ts, line 231:
<comment>Stale closure: `isWaitingForResponse` and `isCompacting` are captured at effect setup time and won't reflect current state. The useEffect dependency array is `[assistant]` but uses these state variables. Consider adding `isWaitingForResponse`, `isCompacting`, and `handleUserMessage` to the dependency array to ensure the handler always checks current state.</comment>
<file context>
@@ -226,6 +226,16 @@ export function useChat({
logger.info("Ocean-bus event queued for processing");
+
+ // If LLM is idle, trigger queue processing
+ if (!isWaitingForResponse && !isCompacting) {
+ const queuedMessageData = messageQueue.getNextMessage();
+ if (queuedMessageData) {
</file context>

Pull Request: Message Normalization for Ollama Model Compatibility
Summary
This PR adds message normalization to handle model-specific formatting requirements when using Ollama's OpenAI-compatible endpoint with MCP (Model Context Protocol) tool calling.
Fixes: #9249
Problem
Certain Ollama models fail during MCP tool calling in the second turn (after tool execution) due to message formatting incompatibilities:
400 Bad Request: Unexpected role 'system' after role 'tool'400 Bad Request: Invalid 'tool_calls': unknown variant 'index'Solution
Added a message normalization layer in
streamChatResponse.tsthat detects model family and applies appropriate fixes before sending to Ollama API:Changes
New Files
extensions/cli/src/util/messageNormalizer.ts- Message normalization utilityModified Files
extensions/cli/src/stream/streamChatResponse.ts- Integration point for normalizationTesting
Tested with Continue CLI using multiple Ollama cloud models and MCP servers (reachy-mini, filesystem):
✅ Working Models (MCP Tool Calling Confirmed)
❌ Known Limitation
indexfield added after normalization by OpenAI adapter layerTest Procedure
Implementation Details
Message Normalization Logic:
Integration Point:
Applied after
convertFromUnifiedHistoryWithSystemMessagebut beforechatCompletionStreamWithBackoffinstreamChatResponse.ts(line 265).Backward Compatibility
Future Work
indexfield issue for potential fix in OpenAI adapter layerChecklist
Related Issues
Screenshots/Logs
Before (Gemma3 error):
After (DeepSeek V3.1 working):
Additional Context
This fix enables robust MCP tool calling across a wide range of Ollama models, improving the developer experience when using Continue CLI with local LLMs.
Summary by cubic
Add Ollama-specific message normalization to fix MCP tool-calling (Mistral/Ministral system-before-tool; remove Gemma tool_calls index), and an ocean-bus SSE subscriber that queues DM events, formats them as prompts, and triggers processing when idle for autonomous handling in Continue CLI. Fixes #9249.
Written for commit a329265. Summary will update automatically on new commits.