Add multi-provider AI config safeguards

This commit is contained in:
2026-02-28 16:35:43 +03:00
parent ea3ab4ff84
commit ff52b75073
4 changed files with 466 additions and 69 deletions

View File

@@ -1,5 +1,32 @@
PERPLEXITY_API_KEY=your_perplexity_api_key_here # Оставьте незакомментированным только один AI API KEY.
# Если одновременно указать несколько AI ключей, колонка выдаст ошибку.
AI_PROVIDER=
# Perplexity
# PERPLEXITY_API_KEY=your_perplexity_api_key_here
PERPLEXITY_MODEL=llama-3.1-sonar-small-128k-chat PERPLEXITY_MODEL=llama-3.1-sonar-small-128k-chat
PERPLEXITY_API_URL=https://api.perplexity.ai/chat/completions
# OpenAI
# OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4o-mini
OPENAI_API_URL=https://api.openai.com/v1/chat/completions
# Gemini
# GEMINI_API_KEY=your_gemini_api_key_here
GEMINI_MODEL=gemini-2.5-flash
GEMINI_API_URL=https://generativelanguage.googleapis.com/v1beta/openai/chat/completions
# Z.ai
# ZAI_API_KEY=your_zai_api_key_here
ZAI_MODEL=glm-5
ZAI_API_URL=https://api.z.ai/api/paas/v4/chat/completions
# Anthropic Claude
# ANTHROPIC_API_KEY=your_anthropic_api_key_here
ANTHROPIC_MODEL=claude-sonnet-4-20250514
ANTHROPIC_API_URL=https://api.anthropic.com/v1/messages
ANTHROPIC_API_VERSION=2023-06-01
DEEPGRAM_API_KEY=your_deepgram_api_key_here DEEPGRAM_API_KEY=your_deepgram_api_key_here
PORCUPINE_ACCESS_KEY=your_porcupine_access_key_here PORCUPINE_ACCESS_KEY=your_porcupine_access_key_here
PORCUPINE_SENSITIVITY=0.8 PORCUPINE_SENSITIVITY=0.8

View File

@@ -24,7 +24,7 @@
- Follow-up окно 4 секунды после ответа: если пользователь молчит, ассистент возвращается к ожиданию wake word. - Follow-up окно 4 секунды после ответа: если пользователь молчит, ассистент возвращается к ожиданию wake word.
- Распознавание речи через Deepgram (WebSocket, VAD, fast stop). - Распознавание речи через Deepgram (WebSocket, VAD, fast stop).
- Озвучка через Silero TTS (RU + EN, с прерыванием по wake word). - Озвучка через Silero TTS (RU + EN, с прерыванием по wake word).
- AI-диалог через Perplexity API со streaming-ответом и контекстом. - AI-диалог через Perplexity, OpenAI, Gemini, Z.ai и Anthropic Claude API со streaming-ответом и контекстом.
- Перевод RU -> EN и EN -> RU. - Перевод RU -> EN и EN -> RU.
- Погода: текущий прогноз по городу по умолчанию или по названию города. - Погода: текущий прогноз по городу по умолчанию или по названию города.
- Таймеры, будильники (включая будни/выходные), секундомеры. - Таймеры, будильники (включая будни/выходные), секундомеры.
@@ -73,11 +73,19 @@ cp .env.example .env
Минимально обязательные переменные: Минимально обязательные переменные:
```ini ```ini
PERPLEXITY_API_KEY=... AI_PROVIDER= # опционально; можно оставить пустым
# Раскомментируйте только один AI API KEY:
# PERPLEXITY_API_KEY=...
# OPENAI_API_KEY=...
# GEMINI_API_KEY=...
# ZAI_API_KEY=...
# ANTHROPIC_API_KEY=...
DEEPGRAM_API_KEY=... DEEPGRAM_API_KEY=...
PORCUPINE_ACCESS_KEY=... PORCUPINE_ACCESS_KEY=...
``` ```
Если одновременно оставить несколько AI API key, ассистент вернет ошибку: он не будет выбирать провайдера наугад.
### 4) Запуск ### 4) Запуск
```bash ```bash
@@ -92,8 +100,23 @@ python run.py
| Переменная | Обязательно | По умолчанию | Назначение | | Переменная | Обязательно | По умолчанию | Назначение |
|---|---|---|---| |---|---|---|---|
| `PERPLEXITY_API_KEY` | Да | - | Ключ Perplexity API | | `AI_PROVIDER` | Нет | `perplexity` | Опциональный провайдер AI (`perplexity`, `openai`, `gemini`, `zai`, `anthropic`; также понимает `claude`) |
| `PERPLEXITY_API_KEY` | Да* | - | Ключ Perplexity API (*если выбран Perplexity и только этот AI ключ активен) |
| `PERPLEXITY_MODEL` | Нет | `llama-3.1-sonar-small-128k-chat` | Модель Perplexity | | `PERPLEXITY_MODEL` | Нет | `llama-3.1-sonar-small-128k-chat` | Модель Perplexity |
| `PERPLEXITY_API_URL` | Нет | `https://api.perplexity.ai/chat/completions` | Endpoint Perplexity Chat Completions |
| `OPENAI_API_KEY` | Да* | - | Ключ OpenAI API (*если выбран OpenAI и только этот AI ключ активен) |
| `OPENAI_MODEL` | Нет | `gpt-4o-mini` | Модель OpenAI |
| `OPENAI_API_URL` | Нет | `https://api.openai.com/v1/chat/completions` | Endpoint OpenAI Chat Completions |
| `GEMINI_API_KEY` | Да* | - | Ключ Google Gemini API (*если выбран Gemini и только этот AI ключ активен) |
| `GEMINI_MODEL` | Нет | `gemini-2.5-flash` | Модель Gemini |
| `GEMINI_API_URL` | Нет | `https://generativelanguage.googleapis.com/v1beta/openai/chat/completions` | OpenAI-compatible endpoint Gemini |
| `ZAI_API_KEY` | Да* | - | Ключ Z.ai API (*если выбран Z.ai и только этот AI ключ активен) |
| `ZAI_MODEL` | Нет | `glm-5` | Модель Z.ai |
| `ZAI_API_URL` | Нет | `https://api.z.ai/api/paas/v4/chat/completions` | Endpoint Z.ai Chat Completions |
| `ANTHROPIC_API_KEY` | Да* | - | Ключ Anthropic API (*если выбран Anthropic и только этот AI ключ активен) |
| `ANTHROPIC_MODEL` | Нет | `claude-sonnet-4-20250514` | Модель Claude |
| `ANTHROPIC_API_URL` | Нет | `https://api.anthropic.com/v1/messages` | Endpoint Anthropic Messages API |
| `ANTHROPIC_API_VERSION` | Нет | `2023-06-01` | Версия Anthropic API |
| `DEEPGRAM_API_KEY` | Да | - | Ключ Deepgram STT | | `DEEPGRAM_API_KEY` | Да | - | Ключ Deepgram STT |
| `PORCUPINE_ACCESS_KEY` | Да | - | Ключ PicoVoice Porcupine | | `PORCUPINE_ACCESS_KEY` | Да | - | Ключ PicoVoice Porcupine |
| `PORCUPINE_SENSITIVITY` | Нет | `0.8` | Чувствительность wake word | | `PORCUPINE_SENSITIVITY` | Нет | `0.8` | Чувствительность wake word |
@@ -121,6 +144,8 @@ python run.py
| Игра | `Давай сыграем в города` | | Игра | `Давай сыграем в города` |
| Управление диалогом | `Повтори`, `Стоп`, `Хватит` | | Управление диалогом | `Повтори`, `Стоп`, `Хватит` |
Память текущего диалога, история сообщений и `ROLE_JSON` системной роли сохраняются для всех поддерживаемых AI-провайдеров.
## Полезные команды ## Полезные команды
| Команда | Что делает | | Команда | Что делает |

View File

@@ -1,11 +1,33 @@
"""AI module for Perplexity API integration.""" """AI module with pluggable providers."""
# Модуль общения с искусственным интеллектом (Perplexity API). # Модуль общения с искусственным интеллектом.
# Обрабатывает запросы пользователя и переводы. # Обрабатывает запросы пользователя и переводы через выбранный API-провайдер.
import json
import re
from typing import Optional
import requests import requests
import re
from .config import PERPLEXITY_API_KEY, PERPLEXITY_MODEL, PERPLEXITY_API_URL from .config import (
AI_PROVIDER,
ANTHROPIC_API_KEY,
ANTHROPIC_API_URL,
ANTHROPIC_API_VERSION,
ANTHROPIC_MODEL,
GEMINI_API_KEY,
GEMINI_API_URL,
GEMINI_MODEL,
OPENAI_API_KEY,
OPENAI_API_URL,
OPENAI_MODEL,
PERPLEXITY_API_KEY,
PERPLEXITY_API_URL,
PERPLEXITY_MODEL,
ZAI_API_KEY,
ZAI_API_URL,
ZAI_MODEL,
)
_HTTP = requests.Session() _HTTP = requests.Session()
@@ -32,10 +54,311 @@ No explanations, no quotes, no comments.
Separate variants with " / " (space slash space). Separate variants with " / " (space slash space).
Keep the translation максимально кратким и естественным, без лишних слов.""" Keep the translation максимально кратким и естественным, без лишних слов."""
_PROVIDER_ALIASES = {
"": "perplexity",
"anthropic": "anthropic",
"claude": "anthropic",
"claude_anthropic": "anthropic",
"gemini": "gemini",
"google": "gemini",
"openai": "openai",
"perplexity": "perplexity",
"z.ai": "zai",
"z-ai": "zai",
"z_ai": "zai",
"zai": "zai",
}
_PROVIDER_SETTINGS = {
"perplexity": {
"provider": "perplexity",
"protocol": "openai_compatible",
"api_key": PERPLEXITY_API_KEY,
"model": PERPLEXITY_MODEL,
"api_url": PERPLEXITY_API_URL,
"name": "Perplexity",
"key_var": "PERPLEXITY_API_KEY",
"model_var": "PERPLEXITY_MODEL",
},
"openai": {
"provider": "openai",
"protocol": "openai_compatible",
"api_key": OPENAI_API_KEY,
"model": OPENAI_MODEL,
"api_url": OPENAI_API_URL,
"name": "OpenAI",
"key_var": "OPENAI_API_KEY",
"model_var": "OPENAI_MODEL",
},
"gemini": {
"provider": "gemini",
"protocol": "openai_compatible",
"api_key": GEMINI_API_KEY,
"model": GEMINI_MODEL,
"api_url": GEMINI_API_URL,
"name": "Gemini",
"key_var": "GEMINI_API_KEY",
"model_var": "GEMINI_MODEL",
},
"zai": {
"provider": "zai",
"protocol": "openai_compatible",
"api_key": ZAI_API_KEY,
"model": ZAI_MODEL,
"api_url": ZAI_API_URL,
"name": "Z.ai",
"key_var": "ZAI_API_KEY",
"model_var": "ZAI_MODEL",
"extra_headers": {
"Accept-Language": "en-US,en",
},
},
"anthropic": {
"provider": "anthropic",
"protocol": "anthropic",
"api_key": ANTHROPIC_API_KEY,
"model": ANTHROPIC_MODEL,
"api_url": ANTHROPIC_API_URL,
"api_version": ANTHROPIC_API_VERSION,
"name": "Anthropic Claude",
"key_var": "ANTHROPIC_API_KEY",
"model_var": "ANTHROPIC_MODEL",
},
}
def _has_active_api_key(value) -> bool:
return bool(str(value or "").strip())
def _normalize_provider_name(provider_name: str) -> str:
normalized = (provider_name or "").strip().lower()
return _PROVIDER_ALIASES.get(normalized, normalized)
def _get_provider_settings():
configured = [
cfg
for cfg in _PROVIDER_SETTINGS.values()
if _has_active_api_key(cfg.get("api_key"))
]
if len(configured) == 1:
cfg = configured[0]
requested = _normalize_provider_name(AI_PROVIDER)
if requested and requested in _PROVIDER_SETTINGS and requested != cfg["provider"]:
print(
f"⚠️ AI_PROVIDER={AI_PROVIDER!r} не совпадает с единственным "
f"активным ключом {cfg['name']}. Используем {cfg['name']}."
)
return cfg, None
if len(configured) > 1:
names = ", ".join(cfg["name"] for cfg in configured)
return None, (
"Обнаружено несколько AI API ключей. Оставьте незакомментированным "
f"только один ключ. Сейчас активны: {names}. "
"Колонка не знает, какой AI использовать."
)
provider = _normalize_provider_name(AI_PROVIDER)
cfg = _PROVIDER_SETTINGS.get(provider)
if cfg:
return cfg, None
supported = ", ".join(sorted(_PROVIDER_SETTINGS))
print(
f"⚠️ Неизвестный AI_PROVIDER={AI_PROVIDER!r}, используем Perplexity. "
f"Поддерживаются: {supported}."
)
return _PROVIDER_SETTINGS["perplexity"], None
def _content_to_text(content) -> str:
if isinstance(content, str):
return content
if isinstance(content, list):
parts = []
for item in content:
if isinstance(item, dict):
text = item.get("text")
if text:
parts.append(str(text))
elif item is not None:
parts.append(str(item))
return "".join(parts)
if content is None:
return ""
return str(content)
def _get_provider_config_error(cfg) -> Optional[str]:
if not cfg:
return "Не настроен AI-провайдер. Проверьте файл .env."
if not cfg["api_key"]:
return f"Не настроен {cfg['key_var']}. Проверьте файл .env."
if not cfg["model"]:
return f"Не настроен {cfg['model_var']}. Проверьте файл .env."
return None
def _build_headers(cfg):
if cfg["protocol"] == "anthropic":
return {
"x-api-key": cfg["api_key"],
"anthropic-version": cfg["api_version"],
"Content-Type": "application/json",
}
headers = {
"Authorization": f"Bearer {cfg['api_key']}",
"Content-Type": "application/json",
}
headers.update(cfg.get("extra_headers") or {})
return headers
def _split_system_messages(messages):
system_parts = []
chat_messages = []
for message in messages:
role = str(message.get("role") or "").strip().lower()
content = _content_to_text(message.get("content"))
if role == "system":
if content:
system_parts.append(content)
continue
if role not in ("user", "assistant"):
role = "user"
chat_messages.append({"role": role, "content": content})
return "\n\n".join(system_parts), chat_messages
def _build_payload(cfg, messages, max_tokens, temperature, stream):
if cfg["protocol"] == "anthropic":
system_prompt, chat_messages = _split_system_messages(messages)
payload = {
"model": cfg["model"],
"messages": chat_messages,
"max_tokens": max_tokens,
"temperature": temperature,
"stream": stream,
}
if system_prompt:
payload["system"] = system_prompt
return payload
return {
"model": cfg["model"],
"messages": messages,
"max_tokens": max_tokens,
"temperature": temperature,
"stream": stream,
}
def _extract_openai_compatible_content(data: dict) -> str:
choices = data.get("choices") or []
if not choices:
return ""
message = choices[0].get("message") or {}
return _content_to_text(message.get("content"))
def _extract_anthropic_content(data: dict) -> str:
return _content_to_text(data.get("content"))
def _extract_response_content(cfg, data: dict) -> str:
if cfg["protocol"] == "anthropic":
return _extract_anthropic_content(data)
return _extract_openai_compatible_content(data)
def _iter_openai_compatible_stream(response):
for line in response.iter_lines(decode_unicode=True):
if not line or not line.startswith("data:"):
continue
data_str = line[5:].strip()
if data_str == "[DONE]":
break
try:
data_json = json.loads(data_str)
except json.JSONDecodeError:
continue
choices = data_json.get("choices") or []
if not choices:
continue
delta = choices[0].get("delta") or {}
content = delta.get("content", "")
if isinstance(content, str):
if content:
yield content
continue
if isinstance(content, list):
for item in content:
if isinstance(item, dict):
text = item.get("text")
if text:
yield str(text)
def _iter_anthropic_stream(response):
for line in response.iter_lines(decode_unicode=True):
if not line or not line.startswith("data:"):
continue
data_str = line[5:].strip()
if data_str == "[DONE]":
break
try:
data_json = json.loads(data_str)
except json.JSONDecodeError:
continue
chunk_type = data_json.get("type")
if chunk_type == "content_block_start":
content_block = data_json.get("content_block") or {}
text = content_block.get("text")
if text:
yield str(text)
elif chunk_type == "content_block_delta":
delta = data_json.get("delta") or {}
text = delta.get("text")
if text:
yield str(text)
def _iter_stream_chunks(cfg, response):
if cfg["protocol"] == "anthropic":
yield from _iter_anthropic_stream(response)
return
yield from _iter_openai_compatible_stream(response)
def _log_request_exception(cfg, error: Exception):
details = ""
response = getattr(error, "response", None)
if response is not None:
body = (response.text or "").strip()
if body:
details = f" | body={body[:400]}"
print(f"❌ Ошибка API ({cfg['name']}): {error}{details}")
def _send_request(messages, max_tokens, temperature, error_text): def _send_request(messages, max_tokens, temperature, error_text):
""" """
Внутренняя функция для отправки HTTP-запроса к Perplexity API. Внутренняя функция для отправки HTTP-запроса к выбранному AI-провайдеру.
Args: Args:
messages: Список сообщений (история чата). messages: Список сообщений (история чата).
@@ -43,34 +366,33 @@ def _send_request(messages, max_tokens, temperature, error_text):
temperature: "Креативность" (0.2 - строго, 1.0 - креативно). temperature: "Креативность" (0.2 - строго, 1.0 - креативно).
error_text: Текст ошибки для пользователя в случае сбоя. error_text: Текст ошибки для пользователя в случае сбоя.
""" """
if not PERPLEXITY_API_KEY: cfg, selection_error = _get_provider_settings()
return "Не настроен PERPLEXITY_API_KEY. Проверьте файл .env." if selection_error:
headers = { return selection_error
"Authorization": f"Bearer {PERPLEXITY_API_KEY}", config_error = _get_provider_config_error(cfg)
"Content-Type": "application/json", if config_error:
} return config_error
payload = {
"model": PERPLEXITY_MODEL,
"messages": messages,
"max_tokens": max_tokens,
"temperature": temperature,
"stream": False # Убираем стриминг для более быстрого ответа
}
try: try:
response = _HTTP.post( response = _HTTP.post(
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=15 # Уменьшаем таймаут cfg["api_url"],
headers=_build_headers(cfg),
json=_build_payload(cfg, messages, max_tokens, temperature, stream=False),
timeout=15,
) )
response.raise_for_status() # Проверка на ошибки HTTP (4xx, 5xx) response.raise_for_status()
data = response.json() data = response.json()
return data["choices"][0]["message"]["content"] content = _extract_response_content(cfg, data)
if not content:
return "Не удалось обработать ответ от AI."
return content
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
return "Извините, сервер не отвечает. Попробуйте позже." return f"Извините, сервер {cfg['name']} не отвечает. Попробуйте позже."
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as error:
print(f"❌ Ошибка API: {e}") _log_request_exception(cfg, error)
return error_text return error_text
except (KeyError, IndexError) as e: except Exception as error:
print(f"❌ Ошибка парсинга ответа: {e}") print(f"❌ Ошибка парсинга ответа ({cfg['name']}): {error}")
return "Не удалось обработать ответ от AI." return "Не удалось обработать ответ от AI."
@@ -109,8 +431,13 @@ def ask_ai_stream(messages_history: list):
""" """
Generator that yields chunks of the AI response as they arrive. Generator that yields chunks of the AI response as they arrive.
""" """
if not PERPLEXITY_API_KEY: cfg, selection_error = _get_provider_settings()
yield "Не настроен ключ PERPLEXITY_API_KEY. Проверьте файл .env." if selection_error:
yield selection_error
return
config_error = _get_provider_config_error(cfg)
if config_error:
yield config_error
return return
if not messages_history: if not messages_history:
yield "Извините, я не расслышал вашу команду." yield "Извините, я не расслышал вашу команду."
@@ -122,46 +449,29 @@ def ask_ai_stream(messages_history: list):
if msg["role"] == "user": if msg["role"] == "user":
last_user_message = msg["content"] last_user_message = msg["content"]
break break
print(f"🤖 Запрос к AI (Stream): {last_user_message}") print(f"🤖 Запрос к AI ({cfg['name']}, Stream): {last_user_message}")
messages = [{"role": "system", "content": SYSTEM_PROMPT}] + list(messages_history) messages = [{"role": "system", "content": SYSTEM_PROMPT}] + list(messages_history)
headers = {
"Authorization": f"Bearer {PERPLEXITY_API_KEY}",
"Content-Type": "application/json",
}
payload = {
"model": PERPLEXITY_MODEL,
"messages": messages,
"max_tokens": 500,
"temperature": 1.0,
"stream": True, # Enable streaming
}
try: try:
response = _HTTP.post( response = _HTTP.post(
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=15, stream=True # Уменьшаем таймаут cfg["api_url"],
headers=_build_headers(cfg),
json=_build_payload(cfg, messages, 500, 1.0, stream=True),
timeout=15,
stream=True,
) )
response.raise_for_status() response.raise_for_status()
import json for chunk in _iter_stream_chunks(cfg, response):
yield chunk
for line in response.iter_lines(decode_unicode=True): except requests.exceptions.Timeout:
if line: yield f"Извините, сервер {cfg['name']} не отвечает. Попробуйте позже."
line_text = line except requests.exceptions.RequestException as error:
if line_text.startswith("data: "): _log_request_exception(cfg, error)
data_str = line_text[6:] # Skip "data: " yield "Произошла ошибка связи."
if data_str == "[DONE]": except Exception as error:
break print(f"❌ Streaming Error ({cfg['name']}): {error}")
try:
data_json = json.loads(data_str)
content = data_json["choices"][0]["delta"].get("content", "")
if content:
yield content
except json.JSONDecodeError:
continue
except Exception as e:
print(f"❌ Streaming Error: {e}")
yield "Произошла ошибка связи." yield "Произошла ошибка связи."

View File

@@ -17,12 +17,47 @@ BASE_DIR = Path(__file__).resolve().parents[2]
# Загружаем переменные из файла .env в корневом каталоге # Загружаем переменные из файла .env в корневом каталоге
load_dotenv(BASE_DIR / ".env") load_dotenv(BASE_DIR / ".env")
# --- Настройки AI (Perplexity) --- # --- Настройки AI ---
# API ключ для доступа к нейросети # AI_PROVIDER опционален. Приоритет у единственного активного AI API key.
# Если активных ключей несколько, AI-модуль вернет ошибку конфигурации.
AI_PROVIDER = os.getenv("AI_PROVIDER", "perplexity").strip().lower()
# Perplexity
PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_API_KEY") PERPLEXITY_API_KEY = os.getenv("PERPLEXITY_API_KEY")
# Модель, которую будем использовать (по умолчанию llama-3.1-sonar-small-128k-chat)
PERPLEXITY_MODEL = os.getenv("PERPLEXITY_MODEL", "llama-3.1-sonar-small-128k-chat") PERPLEXITY_MODEL = os.getenv("PERPLEXITY_MODEL", "llama-3.1-sonar-small-128k-chat")
PERPLEXITY_API_URL = "https://api.perplexity.ai/chat/completions" PERPLEXITY_API_URL = os.getenv(
"PERPLEXITY_API_URL", "https://api.perplexity.ai/chat/completions"
)
# OpenAI
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o-mini")
OPENAI_API_URL = os.getenv(
"OPENAI_API_URL", "https://api.openai.com/v1/chat/completions"
)
# Gemini (через официальный OpenAI-compatible endpoint Google)
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
GEMINI_MODEL = os.getenv("GEMINI_MODEL", "gemini-2.5-flash")
GEMINI_API_URL = os.getenv(
"GEMINI_API_URL",
"https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
)
# Z.ai
ZAI_API_KEY = os.getenv("ZAI_API_KEY")
ZAI_MODEL = os.getenv("ZAI_MODEL", "glm-5")
ZAI_API_URL = os.getenv(
"ZAI_API_URL", "https://api.z.ai/api/paas/v4/chat/completions"
)
# Anthropic Claude
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
ANTHROPIC_MODEL = os.getenv("ANTHROPIC_MODEL", "claude-sonnet-4-20250514")
ANTHROPIC_API_URL = os.getenv(
"ANTHROPIC_API_URL", "https://api.anthropic.com/v1/messages"
)
ANTHROPIC_API_VERSION = os.getenv("ANTHROPIC_API_VERSION", "2023-06-01")
# --- Настройки распознавания речи (Deepgram) --- # --- Настройки распознавания речи (Deepgram) ---
# Ключ для облачного STT (Speech-to-Text) # Ключ для облачного STT (Speech-to-Text)