Explorer
/opt/struktur/lead-engine/llm/openai_provider.py
← Zurück ↓ Download
"""
OpenAI Provider für LLM Enrichment.
Verwendet gpt-5-nano (Level 1) oder gpt-5-mini (Level 2).
"""

import os
import json
from typing import Dict, Any

from .base import LLMProvider

try:
    from openai import OpenAI
    HAS_OPENAI = True
except ImportError:
    HAS_OPENAI = False


class OpenAIProvider(LLMProvider):
    """OpenAI API Provider mit JSON-Response."""

    def __init__(self, model: str = None):
        if not HAS_OPENAI:
            raise ImportError("openai nicht installiert. Bitte: pip install openai")

        model = model or os.getenv('OPENAI_MODEL_LOW', 'gpt-4o-mini')
        super().__init__(model)

        self.client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
        self.tokens_used = 0

    def extract(self, prompt: str) -> Dict[str, Any]:
        """
        Ruft OpenAI API auf und parsed JSON Response.

        Args:
            prompt: Der Enrichment-Prompt

        Returns:
            Dict mit extrahierten Daten

        Raises:
            ValueError: Bei API-Fehler oder JSON-Parse-Fehler
        """
        try:
            # gpt-5-* Modelle: Reasoning-Modelle, keine temperature, kein response_format
            # gpt-4.1-* Modelle: Standard, temperature + response_format nutzbar
            is_reasoning = self.model.startswith('gpt-5')

            if is_reasoning:
                response = self.client.chat.completions.create(
                    model=self.model,
                    messages=[{'role': 'user', 'content': prompt}],
                    max_completion_tokens=10000,  # Reasoning braucht Platz
                )
            else:
                response = self.client.chat.completions.create(
                    model=self.model,
                    messages=[{'role': 'user', 'content': prompt}],
                    max_tokens=2000,
                    temperature=0.2,
                    response_format={'type': 'json_object'},
                )

            # Token-Tracking
            self.tokens_used = (
                response.usage.prompt_tokens + response.usage.completion_tokens
            )

            # Response extrahieren und JSON parsen
            raw_text = response.choices[0].message.content.strip()
            data = json.loads(raw_text)

            return data

        except json.JSONDecodeError as e:
            raise ValueError(f"JSON_PARSE_ERROR: {str(e)}")
        except Exception as e:
            raise ValueError(f"OPENAI_API_ERROR: {str(e)}")