perf: switch all AI calls to native Ollama endpoint, add streaming + keep_alive

Tüm AI çağrıları artık ortak lib/aiClient.ts üzerinden gidiyor:
- /v1/chat/completions yerine native /api/chat (think:false burada
  güvenilir çalışıyor)
- keep_alive:30m ile model her istekte bellekten atılıp soğuk
  başlamıyor
- num_predict ile öngörülemez uzunlukta üretime üst sınır
- İnteraktif sohbet uç noktaları (chatMessage, sendThreadMessage)
  artık SSE ile token token akıtıyor
- Dosya sohbetinde varsayılan olarak belgenin tam metni değil analiz
  özeti gönderiliyor; tam metin sadece kullanıcı açıkça isterse
  eklenir

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
mstfyldz
2026-08-10 00:21:49 +03:00
co-authored by Claude Sonnet 5
parent 0318f2ac77
commit bcc78ce304
5 changed files with 239 additions and 150 deletions
+9 -25
View File
@@ -4,6 +4,7 @@ import { AuthenticatedRequest } from '../middleware/auth';
import * as officeParser from 'officeparser';
import Tesseract from 'tesseract.js';
import { findOrCreateCaseByTitle } from '../lib/caseLookup';
import { callOllama } from '../lib/aiClient';
import { getAiConfig } from '../lib/aiConfig';
const heicConvert = require('heic-convert');
@@ -68,30 +69,13 @@ async function callAiModel(systemPrompt: string, userText: string): Promise<{ re
return { resultJson, aiModel };
}
export async function callAiModelRaw(systemPrompt: string, userText: string): Promise<{ aiContent: string; aiModel: string }> {
const { runpodUrl, aiModel } = getAiConfig();
const response = await fetch(`${runpodUrl}/v1/chat/completions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: aiModel,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userText.substring(0, 15000) }
],
think: false,
stream: false,
}),
});
if (!response.ok) {
throw new Error(`AI Model request failed: ${response.statusText}`);
}
const data = await response.json();
const aiContent = data.message?.content || (data.choices && data.choices[0]?.message?.content) || '';
return { aiContent: String(aiContent).trim(), aiModel };
export async function callAiModelRaw(systemPrompt: string, userText: string, numPredict = 1500): Promise<{ aiContent: string; aiModel: string }> {
const { aiModel } = getAiConfig();
const aiContent = await callOllama([
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userText.substring(0, 15000) },
], { numPredict });
return { aiContent, aiModel };
}
// uploadDocument, registerLocalDocument'in kullandığı genel (kısa, alan-alan) analiz adımı.
@@ -109,7 +93,7 @@ export async function runIddianameAnalysis(extractedText: string): Promise<{ sum
// summarizeCase'in Dosya Özeti raporu için kullandığı adım — çıktı serbest Markdown
// metni, JSON'a çevrilmeye çalışılmıyor.
export async function runCaseNarrativeAnalysis(combinedText: string): Promise<{ summaryJson: any; aiModel: string }> {
const { aiContent, aiModel } = await callAiModelRaw(CASE_NARRATIVE_SYSTEM_PROMPT, combinedText);
const { aiContent, aiModel } = await callAiModelRaw(CASE_NARRATIVE_SYSTEM_PROMPT, combinedText, 2500);
return { summaryJson: { ozet: aiContent }, aiModel };
}