import json import urllib.request import urllib.error class LLMClient: class Message: def __init__(self, msg): self.content = msg.get('content', '') self.tool_calls = msg.get('tool_calls', None) self.warning = None def __init__(self, base_url, model, api_key, timeout=600): self.base_url = base_url.rstrip('/') self.model = model self.api_key = api_key self.timeout = timeout def chat(self, messages, tools=None): url = f"{self.base_url}/chat/completions" payload = { "model": self.model, "messages": messages } if tools: payload["tools"] = tools payload["tool_choice"] = "auto" data = json.dumps(payload).encode('utf-8') req = urllib.request.Request(url, data=data, method='POST') req.add_header('Content-Type', 'application/json') req.add_header('Authorization', f'Bearer {self.api_key}') try: with urllib.request.urlopen(req, timeout=self.timeout) as resp: response = json.loads(resp.read().decode('utf-8')) message = response['choices'][0]['message'] return self.Message(message) except urllib.error.HTTPError as e: body_text = "" try: body_text = e.read().decode('utf-8', errors='replace') except Exception: pass if tools and e.code == 404: try: body = json.loads(body_text) if body_text else {} if 'tool use' in body.get('error', {}).get('message', '').lower(): result = self.chat(messages, tools=None) result.warning = "Tool calling not supported by this model. Running in chat-only mode." return result except Exception: pass detail = f" - {body_text[:500]}" if body_text else "" return self.Message({'content': f"HTTP Error: {e.code} {e.reason}{detail}", 'tool_calls': None}) except Exception as e: return self.Message({'content': f"Error: {str(e)}", 'tool_calls': None})