AI katmanını OpenRouter tek kapısına geçir ve rapor üretimini hızlandır
All checks were successful
Deploy / deploy (push) Successful in 9m30s

- Tüm AI akışı OpenRouter üzerinden (openai SDK, varsayılan model
  deepseek/deepseek-v4-flash, OPENROUTER_MODEL ile değiştirilebilir);
  Anthropic/Gemini/lokal sağlayıcı seçimi ve SDK'ları kaldırıldı.
- Rapor: aday havuzu dilim başına 20 (60 program, ~8k token prompt);
  AI şeması sabit uzunluklu üç liste (hayal 5 / dengeli 13 / garanti 6 —
  dağılım şema düzeyinde zorlanır), sıra + dilim + riskNotu + trendOzeti
  gerçek sıra geçmişinden deterministik üretilir; 184 sn → ~12 sn.
- Sohbet: akış ortası zaman aşımı artık sessizce yutulmaz (kesik cevap
  tam cevap gibi kaydedilmiyordu, kredi iadesi çalışmıyordu); istemci
  hata anında durumu sunucudan senkronlar, boş balon bırakmaz.
- aiHataMesaji, openai SDK'nın .name'i "Error" kalan abort/timeout
  hatalarını da yakalar; meraklisina şeması yeni akışla senkronlandı.
- CI: OPENROUTER_API_KEY / OPENROUTER_MODEL secret'ları; eski AI
  secret'ları kaldırıldı. next.config serverExternalPackages sadeleşti.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-08-07 00:37:54 +03:00
parent 4c618781f6
commit 18d5b42136
15 changed files with 366 additions and 540 deletions

View File

@@ -1,64 +1,52 @@
import Anthropic from "@anthropic-ai/sdk";
import { GoogleGenAI } from "@google/genai";
import OpenAI from "openai";
export type Saglayici = "anthropic" | "gemini";
/** AI_SAGLAYICI env'i ile seçilir; tanımsızsa hangi anahtar varsa o kullanılır. */
export function saglayici(): Saglayici {
const secim = process.env.AI_SAGLAYICI?.trim().toLowerCase();
if (secim === "gemini" || secim === "anthropic") return secim;
return process.env.GEMINI_API_KEY ? "gemini" : "anthropic";
}
/** Seçili sağlayıcının anahtarı tanımlı mı (mock moduna düşülecek mi)? */
export function anahtarVar(): boolean {
return saglayici() === "gemini"
? Boolean(process.env.GEMINI_API_KEY)
: Boolean(process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN);
}
// Model seçimi (bilinçli):
// Anthropic — rapor tek seferlik ve yüksek değerli -> Opus 4.8;
// sohbet kredi başına marj hassas -> Haiku 4.5 (kullanıcı kararı, 22 Tem 2026).
// Gemini — ücretsiz katmanda yalnızca Flash ailesi açık (Pro'da kota 0),
// bu yüzden rapor ve sohbet aynı modeli kullanır; ayrım thinking seviyesinde.
const MODELLER: Record<Saglayici, { rapor: string; sohbet: string }> = {
anthropic: { rapor: "claude-opus-4-8", sohbet: "claude-haiku-4-5" },
gemini: { rapor: "gemini-3.6-flash", sohbet: "gemini-3.6-flash" },
};
// Tüm AI akışı OpenRouter üzerinden tek kapıdan gider (kullanıcı kararı,
// 7 Ağu 2026 — önceki Anthropic/Gemini/lokal sağlayıcı seçimi kaldırıldı).
// Model .env'den değiştirilebilir; rapor ve sohbet istenirse ayrışabilir.
const VARSAYILAN_MODEL = "deepseek/deepseek-v4-flash";
export function raporModeli(): string {
return MODELLER[saglayici()].rapor;
return (
process.env.OPENROUTER_RAPOR_MODEL?.trim() ||
process.env.OPENROUTER_MODEL?.trim() ||
VARSAYILAN_MODEL
);
}
export function sohbetModeli(): string {
return MODELLER[saglayici()].sohbet;
return (
process.env.OPENROUTER_SOHBET_MODEL?.trim() ||
process.env.OPENROUTER_MODEL?.trim() ||
VARSAYILAN_MODEL
);
}
const globalForAi = globalThis as unknown as {
__anthropic?: Anthropic;
__gemini?: GoogleGenAI;
};
export function getAnthropic(): Anthropic {
if (!process.env.ANTHROPIC_API_KEY && !process.env.ANTHROPIC_AUTH_TOKEN) {
throw new Error("AI_KEY_MISSING");
}
if (!globalForAi.__anthropic) {
globalForAi.__anthropic = new Anthropic();
}
return globalForAi.__anthropic;
/** API anahtarı tanımlı mı (mock moduna düşülecek mi)? */
export function anahtarVar(): boolean {
return Boolean(process.env.OPENROUTER_API_KEY);
}
export function getGemini(): GoogleGenAI {
const apiKey = process.env.GEMINI_API_KEY;
const globalForAi = globalThis as unknown as { __openrouter?: OpenAI };
export function getOpenRouter(): OpenAI {
const apiKey = process.env.OPENROUTER_API_KEY;
if (!apiKey) {
throw new Error("AI_KEY_MISSING");
}
if (!globalForAi.__gemini) {
globalForAi.__gemini = new GoogleGenAI({ apiKey });
if (!globalForAi.__openrouter) {
globalForAi.__openrouter = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey,
// Tek retry otoritesi cagri.ts'teki geciciHatadaTekrarla
maxRetries: 0,
// OpenRouter panelindeki kullanım dökümünde uygulama atfı (opsiyonel)
defaultHeaders: {
"HTTP-Referer": "https://kolaytercih.com",
"X-Title": "KolayTercih",
},
});
}
return globalForAi.__gemini;
return globalForAi.__openrouter;
}
export const SORUMLULUK_REDDI =