From 3bb50c4925e5066a9cb1a2f0f4313a0c6ab9aada Mon Sep 17 00:00:00 2001 From: opencode Date: Sun, 5 Jul 2026 07:04:52 +0000 Subject: [PATCH] fix: use /api/chat endpoint for proper multi-turn conversations (issue #10) - Replace /api/generate with /api/chat endpoint - Send messages as proper list with role/content structure - Preserve multi-turn semantics (system/user/assistant roles) - Compatible with OpenAI-compatible endpoints - Filter invalid message roles before sending --- models/api_client.py | 60 ++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/models/api_client.py b/models/api_client.py index 6e531e2..7307d95 100644 --- a/models/api_client.py +++ b/models/api_client.py @@ -106,15 +106,18 @@ class APIClient: self, messages: list, model: str = None, **kwargs ) -> Dict[str, Any]: """ - Get a completion from the LLM using Ollama chat endpoint + Get a completion from the LLM using Ollama chat endpoint. + + Uses /api/chat with proper message list format to preserve + multi-turn conversation semantics (system, user, assistant roles). Args: - messages (list): List of message dictionaries (roles and content) + messages (list): List of message dictionaries with 'role' and 'content' model (str): Model to use **kwargs: Additional parameters for the API Returns: - dict: Response from the LLM + dict: Response from the LLM in OpenAI-compatible format """ if model is None: # Reload config to get latest model setting @@ -123,40 +126,53 @@ class APIClient: current_config = load_config() model = current_config.get("model", "qwen3-coder:30b") - # Convert messages to prompt format expected by Ollama generate endpoint - prompt_text = "" + # Filter messages to only include valid roles for chat endpoint + chat_messages = [] for message in messages: role = message.get("role", "user") content = message.get("content", "") + if role in ("system", "user", "assistant"): + chat_messages.append({"role": role, "content": content}) - # Format messages properly for the model - if role == "system": - prompt_text += f"System: {content}\n\n" - elif role == "assistant": - prompt_text += f"Assistant: {content}\n\n" - else: # user - prompt_text += f"User: {content}\n\n" + if not chat_messages: + return {"error": "No valid messages provided for chat completion"} - # Add instruction for assistant response - prompt_text += "Assistant:" + # Use Ollama chat endpoint with proper message format + data = { + "model": model, + "messages": chat_messages, + "stream": False, + **kwargs, + } - # Prepare the data for Ollama generate endpoint - data = {"model": model, "prompt": prompt_text, "stream": False, **kwargs} - - result = self._make_request("/api/generate", "POST", data) + result = self._make_request("/api/chat", "POST", data) if not result["success"]: return {"error": result["error"]} - # Extract the response from Ollama's generate format + # Extract the response from Ollama's chat format try: response_data = result["data"] - # In Ollama generate responses, the actual text is in the "response" field - if "response" in response_data: + # Ollama chat endpoint returns message in message.content + if "message" in response_data: + return { + "choices": [ + { + "message": { + "role": response_data["message"].get( + "role", "assistant" + ), + "content": response_data["message"].get( + "content", "" + ), + } + } + ] + } + elif "response" in response_data: return { "choices": [{"message": {"content": response_data["response"]}}] } else: - # If we get a different format, return what we found return response_data except Exception as e: return {"error": f"Failed to process chat completion result: {str(e)}"}