| """Shared OpenAI-compatible chat-completion helper. |
| |
| All three slots (Mistral La Plateforme, GitHub Models, Ollama) expose an |
| OpenAI-compatible /v1/chat/completions endpoint, so we use the official |
| openai SDK with a per-provider base_url + api_key. This keeps generation |
| code uniform and makes test mocking trivial (one HTTP shape to mock). |
| """ |
|
|
| from __future__ import annotations |
|
|
| import time |
| from typing import Any, cast |
|
|
| from openai import APIError, OpenAI |
|
|
| from nl_sql.llm.providers.base import ( |
| GenerateRequest, |
| GenerateResponse, |
| ProviderError, |
| ) |
|
|
|
|
| def chat_complete( |
| client: OpenAI, |
| model: str, |
| req: GenerateRequest, |
| ) -> GenerateResponse: |
| """Run a single chat-completion call against an OpenAI-compatible endpoint. |
| |
| Returns a normalized GenerateResponse. Wraps SDK errors into ProviderError so |
| upstream code never needs to care which SDK raised what. |
| """ |
| messages: list[dict[str, str]] = [] |
| if req.system: |
| messages.append({"role": "system", "content": req.system}) |
| messages.append({"role": "user", "content": req.prompt}) |
|
|
| kwargs: dict[str, Any] = { |
| "model": model, |
| "messages": cast("list[Any]", messages), |
| "temperature": req.temperature, |
| "max_tokens": req.max_tokens, |
| } |
| if req.json_mode: |
| |
| |
| kwargs["response_format"] = {"type": "json_object"} |
|
|
| started = time.perf_counter() |
| try: |
| completion = client.chat.completions.create(**kwargs) |
| except APIError as exc: |
| raise ProviderError(f"chat.completions failed for model={model}: {exc}") from exc |
|
|
| latency_ms = (time.perf_counter() - started) * 1000.0 |
| choice = completion.choices[0] |
| text = choice.message.content or "" |
|
|
| usage = completion.usage |
| input_tokens = usage.prompt_tokens if usage else 0 |
| output_tokens = usage.completion_tokens if usage else 0 |
|
|
| return GenerateResponse( |
| text=text, |
| model=completion.model or model, |
| input_tokens=input_tokens, |
| output_tokens=output_tokens, |
| latency_ms=latency_ms, |
| ) |
|
|