klg-asutk-app/backend/app/services/ai_service.py
Yuriy 44b14cc4fd feat: все AI-функции переведены на Anthropic Claude API
- ai_service.py: единый AI-сервис (chat, chat_with_history, analyze_document)
- routes/ai.py: POST /api/v1/ai/chat (chat, summarize, extract_risks, classify, translate)
- config.py: ANTHROPIC_API_KEY, ANTHROPIC_MODEL
- requirements.txt: anthropic>=0.42.0
- api-client.ts: aiApi (chat, summarize, extractRisks)
- CSP: connect-src добавлен https://api.anthropic.com
- app/api/ai-chat: прокси на бэкенд /api/v1/ai/chat (Anthropic)
- legal_agents/llm_client.py: переведён на ai_service (Claude)
- AIAccessSettings: только Claude (Sonnet 4, 3 Sonnet, 3 Opus)
- k8s, .env.example: OPENAI → ANTHROPIC
- package.json: удалена зависимость openai
- Документация: OpenAI/GPT заменены на Claude/Anthropic

Провайдер: исключительно Anthropic Claude
Модель по умолчанию: claude-sonnet-4-20250514

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 15:51:59 +03:00

141 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
AI-сервис КЛГ АСУ ТК — использует исключительно Anthropic Claude API.
Все AI-функции системы проходят через этот модуль.
"""
import logging
import os
from typing import Any
logger = logging.getLogger(__name__)
# Ленивая инициализация клиента
_client = None
def _get_client():
"""Получить или создать клиент Anthropic."""
global _client
if _client is None:
try:
from anthropic import Anthropic
api_key = os.getenv("ANTHROPIC_API_KEY", "")
if not api_key:
logger.warning("ANTHROPIC_API_KEY не задан. AI-функции недоступны.")
return None
_client = Anthropic(api_key=api_key)
except ImportError:
logger.warning("Пакет anthropic не установлен. pip install anthropic")
return None
return _client
def chat(
prompt: str,
system: str = "Ты — AI-ассистент системы КЛГ АСУ ТК (контроль лётной годности). Отвечай на русском языке, точно и по делу.",
model: str = "claude-sonnet-4-20250514",
max_tokens: int = 2048,
temperature: float = 0.3,
) -> str | None:
"""
Отправить запрос к Claude и получить текстовый ответ.
Args:
prompt: Текст запроса пользователя
system: Системный промпт (контекст роли)
model: Модель Claude (claude-sonnet-4-20250514, claude-haiku-4-5-20251001 и т.д.)
max_tokens: Максимум токенов в ответе
temperature: Температура генерации (0.01.0)
Returns:
Текст ответа или None при ошибке
"""
client = _get_client()
if client is None:
return None
try:
response = client.messages.create(
model=model,
max_tokens=max_tokens,
temperature=temperature,
system=system,
messages=[
{"role": "user", "content": prompt}
],
)
# Извлечь текст из ответа
text_blocks = [block.text for block in response.content if block.type == "text"]
return "\n".join(text_blocks) if text_blocks else None
except Exception as e:
logger.error("Anthropic API error: %s", e)
return None
def chat_with_history(
messages: list[dict[str, str]],
system: str = "Ты — AI-ассистент системы КЛГ АСУ ТК.",
model: str = "claude-sonnet-4-20250514",
max_tokens: int = 2048,
) -> str | None:
"""
Отправить беседу с историей к Claude.
Args:
messages: Список сообщений [{"role": "user"|"assistant", "content": "..."}]
system: Системный промпт
model: Модель Claude
max_tokens: Максимум токенов
Returns:
Текст ответа или None
"""
client = _get_client()
if client is None:
return None
try:
response = client.messages.create(
model=model,
max_tokens=max_tokens,
system=system,
messages=messages,
)
text_blocks = [block.text for block in response.content if block.type == "text"]
return "\n".join(text_blocks) if text_blocks else None
except Exception as e:
logger.error("Anthropic API error: %s", e)
return None
def analyze_document(
text: str,
task: str = "summarize",
) -> str | None:
"""
Анализ документа с помощью Claude.
Args:
text: Текст документа
task: Задача — summarize | extract_risks | classify | translate
Returns:
Результат анализа или None
"""
tasks = {
"summarize": "Сделай краткое резюме следующего документа на русском языке:",
"extract_risks": "Извлеки все риски и несоответствия из следующего документа. Формат: список с описанием и уровнем критичности (low/medium/high/critical):",
"classify": "Классифицируй следующий документ по типу (директива ЛГ, сервисный бюллетень, программа ТО, акт проверки, сертификат, иное). Укажи тип и краткое обоснование:",
"translate": "Переведи следующий документ на русский язык, сохраняя техническую терминологию авиации:",
}
system_prompt = tasks.get(task, tasks["summarize"])
return chat(prompt=text[:50000], system=system_prompt, max_tokens=4096)
# ─── Совместимость: если где-то вызывался openai ─────────────
# Алиасы для обратной совместимости
def complete(prompt: str, **kwargs) -> str | None:
"""Алиас для chat() — замена openai.Completion."""
return chat(prompt=prompt, **kwargs)