Fix provider handler errors

- Fixed GoogleProviderHandler to return OpenAI-style response format
- Added tools and tool_choice parameters to OllamaProviderHandler (accepted but ignored)
- Fixed OpenAI message building to properly handle tool messages with tool_call_id
- Fixed max_tokens handling to avoid passing null values to APIs
- Converted Ollama response to OpenAI-style format for consistency

This fixes the following errors:
- 'Cannot read properties of undefined (reading '0')' - Google response format issue
- 'OllamaProviderHandler.handle_request() got an unexpected keyword argument 'tools''
- 'for 'role:tool' the following must be satisfied[('messages.23.tool_call_id' : property 'tool_call_id' is missing)]'
- 'Invalid input: expected number, received null' for max_tokens parameter
parent ef580a2b
...@@ -137,33 +137,39 @@ class GoogleProviderHandler(BaseProviderHandler): ...@@ -137,33 +137,39 @@ class GoogleProviderHandler(BaseProviderHandler):
# Build content from messages # Build content from messages
content = "\n\n".join([f"{msg['role']}: {msg['content']}" for msg in messages]) content = "\n\n".join([f"{msg['role']}: {msg['content']}" for msg in messages])
# Build config with only non-None values
config = {"temperature": temperature}
if max_tokens is not None:
config["max_output_tokens"] = max_tokens
# Generate content using the google-genai client # Generate content using the google-genai client
response = self.client.models.generate_content( response = self.client.models.generate_content(
model=model, model=model,
contents=content, contents=content,
config={ config=config
"temperature": temperature,
"max_output_tokens": max_tokens
}
) )
logging.info(f"GoogleProviderHandler: Response received: {response}") logging.info(f"GoogleProviderHandler: Response received: {response}")
self.record_success() self.record_success()
# Return the response as a dictionary # Return the response in OpenAI-style format
return { return {
"candidates": [{ "id": f"google-{model}-{int(time.time())}",
"content": { "object": "chat.completion",
"parts": [{"text": response.text}], "created": int(time.time()),
"role": "model" "model": model,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": response.text
}, },
"finishReason": "STOP", "finish_reason": "stop"
"index": 0
}], }],
"usageMetadata": { "usage": {
"promptTokenCount": getattr(response, "prompt_token_count", 0), "prompt_tokens": getattr(response, "prompt_token_count", 0),
"candidatesTokenCount": getattr(response, "candidates_token_count", 0), "completion_tokens": getattr(response, "candidates_token_count", 0),
"totalTokenCount": getattr(response, "total_token_count", 0) "total_tokens": getattr(response, "total_token_count", 0)
} }
} }
except Exception as e: except Exception as e:
...@@ -224,20 +230,31 @@ class OpenAIProviderHandler(BaseProviderHandler): ...@@ -224,20 +230,31 @@ class OpenAIProviderHandler(BaseProviderHandler):
request_params = { request_params = {
"model": model, "model": model,
"messages": [], "messages": [],
"max_tokens": max_tokens,
"temperature": temperature, "temperature": temperature,
"stream": stream "stream": stream
} }
# Only add max_tokens if it's not None
if max_tokens is not None:
request_params["max_tokens"] = max_tokens
# Build messages with all fields (including tool_calls and tool_call_id) # Build messages with all fields (including tool_calls and tool_call_id)
for msg in messages: for msg in messages:
message = {"role": msg["role"]} message = {"role": msg["role"]}
# For tool role, tool_call_id is required
if msg["role"] == "tool":
if "tool_call_id" in msg and msg["tool_call_id"] is not None:
message["tool_call_id"] = msg["tool_call_id"]
else:
# Skip tool messages without tool_call_id
logger.warning(f"Skipping tool message without tool_call_id: {msg}")
continue
if "content" in msg and msg["content"] is not None: if "content" in msg and msg["content"] is not None:
message["content"] = msg["content"] message["content"] = msg["content"]
if "tool_calls" in msg and msg["tool_calls"] is not None: if "tool_calls" in msg and msg["tool_calls"] is not None:
message["tool_calls"] = msg["tool_calls"] message["tool_calls"] = msg["tool_calls"]
if "tool_call_id" in msg and msg["tool_call_id"] is not None:
message["tool_call_id"] = msg["tool_call_id"]
if "name" in msg and msg["name"] is not None: if "name" in msg and msg["name"] is not None:
message["name"] = msg["name"] message["name"] = msg["name"]
request_params["messages"].append(message) request_params["messages"].append(message)
...@@ -337,6 +354,10 @@ class OllamaProviderHandler(BaseProviderHandler): ...@@ -337,6 +354,10 @@ class OllamaProviderHandler(BaseProviderHandler):
async def handle_request(self, model: str, messages: List[Dict], max_tokens: Optional[int] = None, async def handle_request(self, model: str, messages: List[Dict], max_tokens: Optional[int] = None,
temperature: Optional[float] = 1.0, stream: Optional[bool] = False, temperature: Optional[float] = 1.0, stream: Optional[bool] = False,
tools: Optional[List[Dict]] = None, tool_choice: Optional[Union[str, Dict]] = None) -> Dict: tools: Optional[List[Dict]] = None, tool_choice: Optional[Union[str, Dict]] = None) -> Dict:
"""
Handle request for Ollama provider.
Note: Ollama doesn't support tools/tool_choice, so these parameters are accepted but ignored.
"""
import logging import logging
import json import json
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -375,13 +396,15 @@ class OllamaProviderHandler(BaseProviderHandler): ...@@ -375,13 +396,15 @@ class OllamaProviderHandler(BaseProviderHandler):
prompt = "\n\n".join([f"{msg['role']}: {msg['content']}" for msg in messages]) prompt = "\n\n".join([f"{msg['role']}: {msg['content']}" for msg in messages])
logger.info(f"Prompt length: {len(prompt)} characters") logger.info(f"Prompt length: {len(prompt)} characters")
# Build options with only non-None values
options = {"temperature": temperature}
if max_tokens is not None:
options["num_predict"] = max_tokens
request_data = { request_data = {
"model": model, "model": model,
"prompt": prompt, "prompt": prompt,
"options": { "options": options,
"temperature": temperature,
"num_predict": max_tokens
},
"stream": False # Explicitly disable streaming for non-streaming requests "stream": False # Explicitly disable streaming for non-streaming requests
} }
...@@ -438,7 +461,27 @@ class OllamaProviderHandler(BaseProviderHandler): ...@@ -438,7 +461,27 @@ class OllamaProviderHandler(BaseProviderHandler):
logger.info(f"Final response: {response_json}") logger.info(f"Final response: {response_json}")
self.record_success() self.record_success()
logger.info(f"=== OllamaProviderHandler.handle_request END ===") logger.info(f"=== OllamaProviderHandler.handle_request END ===")
return response_json
# Convert Ollama response to OpenAI-style format
return {
"id": f"ollama-{model}-{int(time.time())}",
"object": "chat.completion",
"created": int(time.time()),
"model": model,
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": response_json.get("response", "")
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": response_json.get("prompt_eval_count", 0),
"completion_tokens": response_json.get("eval_count", 0),
"total_tokens": response_json.get("prompt_eval_count", 0) + response_json.get("eval_count", 0)
}
}
except Exception as e: except Exception as e:
self.record_failure() self.record_failure()
raise e raise e
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment