""" OpenAI adapter for tool calling using native function calling API. This adapter converts Lyra tool definitions to OpenAI's function calling format and parses OpenAI responses back to Lyra's standardized format. """ import json from typing import Dict, List, Optional from .base import ToolAdapter class OpenAIAdapter(ToolAdapter): """OpenAI-specific adapter using native function calling. OpenAI supports function calling natively through the 'tools' parameter in chat completions. This adapter leverages that capability. """ async def prepare_request( self, messages: List[Dict], tools: List[Dict], tool_choice: Optional[str] = None ) -> Dict: """Convert Lyra tools to OpenAI function calling format. Args: messages: Conversation history tools: Lyra tool definitions tool_choice: "auto", "required", "none", or None Returns: dict: Request payload with OpenAI-formatted tools """ # Convert Lyra tools → OpenAI function calling format openai_tools = [] for tool in tools: openai_tools.append({ "type": "function", "function": { "name": tool["name"], "description": tool["description"], "parameters": { "type": "object", "properties": tool["parameters"], "required": tool.get("required", []) } } }) payload = { "messages": messages, "tools": openai_tools } # Add tool_choice if specified if tool_choice: if tool_choice == "required": payload["tool_choice"] = "required" elif tool_choice == "none": payload["tool_choice"] = "none" else: # "auto" or default payload["tool_choice"] = "auto" return payload async def parse_response(self, response) -> Dict: """Extract tool calls from OpenAI response. Args: response: OpenAI ChatCompletion response object Returns: dict: Standardized Lyra format with content and tool_calls """ message = response.choices[0].message content = message.content if message.content else "" tool_calls = [] # Check if response contains tool calls if hasattr(message, 'tool_calls') and message.tool_calls: for tc in message.tool_calls: try: # Parse arguments (may be JSON string) args = tc.function.arguments if isinstance(args, str): args = json.loads(args) tool_calls.append({ "id": tc.id, "name": tc.function.name, "arguments": args }) except json.JSONDecodeError as e: # If arguments can't be parsed, include error tool_calls.append({ "id": tc.id, "name": tc.function.name, "arguments": {}, "error": f"Failed to parse arguments: {str(e)}" }) return { "content": content, "tool_calls": tool_calls if tool_calls else None } def format_tool_result( self, tool_call_id: str, tool_name: str, result: Dict ) -> Dict: """Format tool result as OpenAI tool message. Args: tool_call_id: ID from the original tool call tool_name: Name of the executed tool result: Tool execution result Returns: dict: Message in OpenAI tool message format """ return { "role": "tool", "tool_call_id": tool_call_id, "name": tool_name, "content": json.dumps(result, ensure_ascii=False) }