feat: AI proxy via papa-app (Anthropic from RU), env and docker-compose

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Yuriy 2026-02-16 06:39:48 +03:00
parent 25be90de7b
commit 9d49c9bb6b
4 changed files with 43 additions and 16 deletions

View File

@ -7,3 +7,9 @@ NODE_ENV=development
# AI — Anthropic Claude (единственный AI-провайдер). Бэкенд: ANTHROPIC_API_KEY # AI — Anthropic Claude (единственный AI-провайдер). Бэкенд: ANTHROPIC_API_KEY
# OPENAI_API_KEY не используется # OPENAI_API_KEY не используется
ANTHROPIC_API_KEY= ANTHROPIC_API_KEY=
# Прокси Anthropic через papa-app на Railway (если Anthropic блокирует с РФ IP)
# AI_PROXY_URL=https://papa-app-production.up.railway.app/api/proxy/anthropic
# AI_PROXY_SECRET=klg-refly-proxy-2026
AI_PROXY_URL=
AI_PROXY_SECRET=

View File

@ -21,8 +21,11 @@ class ChatResponse(BaseModel):
@router.post("/chat", response_model=ChatResponse) @router.post("/chat", response_model=ChatResponse)
async def ai_chat(req: ChatRequest, user=Depends(get_current_user)): async def ai_chat(req: ChatRequest, user=Depends(get_current_user)):
api_key = getattr(settings, "ANTHROPIC_API_KEY", None) or "" api_key = getattr(settings, "ANTHROPIC_API_KEY", None) or ""
if not api_key or api_key == "": proxy_url = getattr(settings, "AI_PROXY_URL", "") or ""
raise HTTPException(400, "AI assistant not configured") proxy_secret = getattr(settings, "AI_PROXY_SECRET", "") or ""
if not proxy_url or not proxy_secret:
if not api_key or api_key == "":
raise HTTPException(400, "AI assistant not configured (set ANTHROPIC_API_KEY or AI_PROXY_URL+AI_PROXY_SECRET)")
db = SessionLocal() db = SessionLocal()
try: try:
@ -52,21 +55,33 @@ async def ai_chat(req: ChatRequest, user=Depends(get_current_user)):
Отвечай на русском языке. Будь конкретным и профессиональным. Отвечай на русском языке. Будь конкретным и профессиональным.
Используй авиационную терминологию где уместно.""" Используй авиационную терминологию где уместно."""
payload = {
"model": getattr(settings, "ANTHROPIC_MODEL", "claude-sonnet-4-20250514"),
"max_tokens": 1024,
"system": system_prompt,
"messages": [{"role": "user", "content": req.message}],
}
async with httpx.AsyncClient(timeout=30.0) as client: async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post( if proxy_url and proxy_secret:
"https://api.anthropic.com/v1/messages", resp = await client.post(
headers={ proxy_url,
"x-api-key": api_key, headers={
"anthropic-version": "2023-06-01", "x-proxy-secret": proxy_secret,
"content-type": "application/json", "content-type": "application/json",
}, },
json={ json=payload,
"model": getattr(settings, "ANTHROPIC_MODEL", "claude-sonnet-4-20250514"), )
"max_tokens": 1024, else:
"system": system_prompt, resp = await client.post(
"messages": [{"role": "user", "content": req.message}], "https://api.anthropic.com/v1/messages",
}, headers={
) "x-api-key": api_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
json=payload,
)
if resp.status_code != 200: if resp.status_code != 200:
raise HTTPException(502, f"AI service error: {resp.status_code}") raise HTTPException(502, f"AI service error: {resp.status_code}")

View File

@ -67,6 +67,9 @@ class Settings(BaseSettings):
# AI (Anthropic Claude) # AI (Anthropic Claude)
ANTHROPIC_API_KEY: str = "" ANTHROPIC_API_KEY: str = ""
ANTHROPIC_MODEL: str = "claude-sonnet-4-20250514" ANTHROPIC_MODEL: str = "claude-sonnet-4-20250514"
# Прокси Anthropic через papa-app (Railway) — для обхода блокировки с российских IP
AI_PROXY_URL: str = ""
AI_PROXY_SECRET: str = ""
@property @property
def database_url(self) -> str: def database_url(self) -> str:

View File

@ -95,6 +95,9 @@ services:
FGIS_ORG_ID: ${FGIS_ORG_ID:-} FGIS_ORG_ID: ${FGIS_ORG_ID:-}
FGIS_API_KEY: ${FGIS_API_KEY:-} FGIS_API_KEY: ${FGIS_API_KEY:-}
FGIS_CERT_PATH: /etc/ssl/fgis/client.pem FGIS_CERT_PATH: /etc/ssl/fgis/client.pem
# Прокси Anthropic через papa-app (Railway) — обход блокировки с российских IP
AI_PROXY_URL: ${AI_PROXY_URL:-}
AI_PROXY_SECRET: ${AI_PROXY_SECRET:-}
ports: ports:
- "8000:8000" - "8000:8000"
volumes: volumes: