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:
co-authored by
Claude Sonnet 5
parent
0318f2ac77
commit
bcc78ce304
@@ -1,58 +1,48 @@
|
||||
import { Response } from 'express';
|
||||
import { supabase } from '../lib/supabase';
|
||||
import { AuthenticatedRequest } from '../middleware/auth';
|
||||
import { getAiConfig } from '../lib/aiConfig';
|
||||
import { callOllama, streamOllama, ChatMsg } from '../lib/aiClient';
|
||||
|
||||
// Kullanıcı bir belgenin TAM metnine bakılmasını açıkça istediğinde (örn. "belgenin
|
||||
// tamamına bak", "tam metni incele") sistem promptuna extracted_text'in tamamı
|
||||
// eklenir. Aksi halde sadece analiz özeti (summary_json) gönderilir — hem promptu
|
||||
// küçültüp yanıtı hızlandırır hem gereksiz token maliyetini önler.
|
||||
const FULL_TEXT_TRIGGER = /tam metn|belgenin tamam|dosyanın tamam|tüm metn|tüm belge|belgeyi (baştan sona|detaylı)/i;
|
||||
|
||||
// İlk mesajdan kısa bir başlık üretir (Claude/ChatGPT'nin yaptığı gibi). Başarısız
|
||||
// olursa null döner — çağıran taraf bu durumda varsayılan başlığı korur, asıl
|
||||
// sohbet cevabını etkilemez. Hem dava-dosyası-siz "Yeni Sohbet" (cases.kind='chat')
|
||||
// hem de chat_threads kayıtları için ortak kullanılır.
|
||||
// hem de chat_threads kayıtları için ortak kullanılır. Çıktı birkaç kelimelik bir
|
||||
// başlık olduğu için num_predict düşük tutulup çağrı hızlandırılıyor.
|
||||
async function generateShortTitle(firstMessage: string): Promise<string | null> {
|
||||
try {
|
||||
const { runpodUrl, aiModel } = getAiConfig();
|
||||
const response = await fetch(`${runpodUrl}/api/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: aiModel,
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'Kullanıcının hukuki sorusunu en fazla 5 kelimelik, kısa ve açıklayıcı bir Türkçe başlığa çevir. Sadece başlığı yaz; tırnak işareti, noktalama veya başka açıklama ekleme.'
|
||||
},
|
||||
{ role: 'user', content: firstMessage.slice(0, 2000) },
|
||||
],
|
||||
think: false,
|
||||
stream: false,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
const data = await response.json();
|
||||
const raw = (data.message?.content || '').toString().trim().replace(/^["'“”]+|["'“”]+$/g, '');
|
||||
if (!raw) return null;
|
||||
return raw.slice(0, 80);
|
||||
const raw = await callOllama([
|
||||
{
|
||||
role: 'system',
|
||||
content: 'Kullanıcının hukuki sorusunu en fazla 5 kelimelik, kısa ve açıklayıcı bir Türkçe başlığa çevir. Sadece başlığı yaz; tırnak işareti, noktalama veya başka açıklama ekleme.'
|
||||
},
|
||||
{ role: 'user', content: firstMessage.slice(0, 2000) },
|
||||
], { numPredict: 30, timeoutMs: 20_000 });
|
||||
const cleaned = raw.trim().replace(/^["'“”]+|["'“”]+$/g, '');
|
||||
return cleaned ? cleaned.slice(0, 80) : null;
|
||||
} catch (err) {
|
||||
console.error('Generate Short Title Error:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// AI modeline chat completion isteği atan ortak yardımcı. RunPod worker uykudaysa
|
||||
// soğuk başlangıç birkaç dakika sürebilir; burada sunucu tarafında bir zaman aşımı
|
||||
// dayatmıyoruz (istemci kendi AbortController'ıyla bekleme süresini yönetiyor) —
|
||||
// böylece yavaş ama başarılı olacak bir yanıtı erkenden kesmiyoruz.
|
||||
async function callAiChat(messages: { role: string; content: string }[]): Promise<string> {
|
||||
const { runpodUrl, aiModel } = getAiConfig();
|
||||
const response = await fetch(`${runpodUrl}/api/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ model: aiModel, messages, think: false, stream: false }),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`AI Model request failed: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data.message?.content || 'Yanıt alınamadı.';
|
||||
function startSse(res: Response) {
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
// Bazı reverse proxy'lerin (nginx) chunked yanıtı ara belleğe alıp token'ları
|
||||
// toplu halde göndermesini önlemek için.
|
||||
res.setHeader('X-Accel-Buffering', 'no');
|
||||
(res as any).flushHeaders?.();
|
||||
}
|
||||
|
||||
function sseWrite(res: Response, payload: Record<string, unknown>) {
|
||||
res.write(`data: ${JSON.stringify(payload)}\n\n`);
|
||||
}
|
||||
|
||||
export const deleteChatHistory = async (req: AuthenticatedRequest, res: Response) => {
|
||||
@@ -73,29 +63,30 @@ export const deleteChatHistory = async (req: AuthenticatedRequest, res: Response
|
||||
}
|
||||
};
|
||||
|
||||
// Bu dosyanın "AyrisLegal'e Sor" sohbeti — yanıtı Server-Sent Events ile token
|
||||
// token akıtır (bkz. lib/aiClient.ts streamOllama). Kullanıcı tüm yanıtı beklemek
|
||||
// yerine yazılırken görür; gerçek üretim süresi aynı kalsa da algılanan hız artar.
|
||||
export const chatMessage = async (req: AuthenticatedRequest, res: Response) => {
|
||||
const { caseId } = req.params;
|
||||
const { content } = req.body;
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!content || !caseId) {
|
||||
return res.status(400).json({ error: 'Missing caseId or content' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { caseId } = req.params;
|
||||
const { content } = req.body;
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!content || !caseId) {
|
||||
return res.status(400).json({ error: 'Missing caseId or content' });
|
||||
}
|
||||
|
||||
// kind='case' olan gerçek dava dosyalarının başlığı UYAP'tan/kullanıcıdan geliyor —
|
||||
// bu asla otomatik yeniden adlandırılmamalı. Otomatik başlık üretimi SADECE
|
||||
// kind='chat' (Sohbet'teki "Yeni Sohbet") kayıtları için geçerli.
|
||||
const { data: caseRow } = await supabase.from('cases').select('kind').eq('id', caseId).maybeSingle();
|
||||
const caseKind = caseRow?.kind || 'case';
|
||||
|
||||
const { data: userMessage, error: userMsgError } = await supabase
|
||||
const { error: userMsgError } = await supabase
|
||||
.from('chat_messages')
|
||||
.insert([{ case_id: caseId, user_id: userId, role: 'user', content }])
|
||||
.select().single();
|
||||
.insert([{ case_id: caseId, user_id: userId, role: 'user', content }]);
|
||||
if (userMsgError) throw userMsgError;
|
||||
|
||||
// 1. Dosyaya ait belge analizlerini çek (EKSİK OLAN KISIM)
|
||||
const { data: analyses } = await supabase
|
||||
.from('analyses')
|
||||
.select('summary_json')
|
||||
@@ -106,7 +97,6 @@ export const chatMessage = async (req: AuthenticatedRequest, res: Response) => {
|
||||
.select('filename, extracted_text')
|
||||
.eq('case_id', caseId);
|
||||
|
||||
// 2. Geçmiş sohbet
|
||||
const { data: pastMessages } = await supabase
|
||||
.from('chat_messages')
|
||||
.select('role, content')
|
||||
@@ -114,35 +104,45 @@ export const chatMessage = async (req: AuthenticatedRequest, res: Response) => {
|
||||
.order('created_at', { ascending: true })
|
||||
.limit(10);
|
||||
|
||||
// 3. Belge bağlamını sistem promptuna göm
|
||||
// Belge bağlamı: varsayılan olarak sadece dosya adları + analiz özeti gönderilir.
|
||||
// Kullanıcı açıkça tam metni isterse (FULL_TEXT_TRIGGER) extracted_text'lerin
|
||||
// tamamı eklenir. Bu, her mesajda tüm belgelerin tam metnini gömüp promptu
|
||||
// şişirmek yerine, çoğu soruda çok daha küçük ve hızlı bir istek üretir.
|
||||
const wantsFullText = FULL_TEXT_TRIGGER.test(content);
|
||||
let documentContext = '';
|
||||
if (documents && documents.length > 0) {
|
||||
documentContext = '\n\nDOSYAYA AİT BELGELER:\n' +
|
||||
documents.map(d => `--- ${d.filename} ---\n${d.extracted_text}`).join('\n\n');
|
||||
if (wantsFullText) {
|
||||
documentContext = '\n\nDOSYAYA AİT BELGELERİN TAM METNİ:\n' +
|
||||
documents.map(d => `--- ${d.filename} ---\n${d.extracted_text}`).join('\n\n');
|
||||
} else {
|
||||
documentContext = '\n\nDOSYAYA AİT BELGELER: ' + documents.map(d => d.filename).join(', ') +
|
||||
'\n(Aşağıda bu belgelerin analiz özeti var. Kullanıcı tam metni isterse — "belgenin tamamına bak" gibi — tam metni isteyebileceğini kendisine hatırlatabilirsin.)';
|
||||
}
|
||||
} else {
|
||||
documentContext = '\n\nBu dosyaya henüz hiçbir belge yüklenmemiştir. Genel hukuki bilgiyle cevap ver, ama bunu kullanıcıya açıkça belirt.';
|
||||
}
|
||||
|
||||
if (analyses && analyses.length > 0) {
|
||||
documentContext += '\n\nÖNCEKİ ANALİZ ÖZETİ:\n' + JSON.stringify(analyses[analyses.length - 1].summary_json);
|
||||
documentContext += '\n\nANALİZ ÖZETİ:\n' + JSON.stringify(analyses[analyses.length - 1].summary_json);
|
||||
}
|
||||
|
||||
const systemPrompt = `Sen bir hukuk asistanısın. Bu dava dosyasıyla ilgili kullanıcının sorularını, aşağıda verilen belge içeriğine dayanarak cevaplıyorsun. Eğer belge yoksa veya soruyu cevaplamaya yetmiyorsa, bunu açıkça belirt, uydurma bilgi verme.${documentContext}`;
|
||||
|
||||
const messages: any[] = [{ role: 'system', content: systemPrompt }];
|
||||
if (pastMessages) {
|
||||
const messages: ChatMsg[] = [{ role: 'system', content: systemPrompt }];
|
||||
if (pastMessages && pastMessages.length > 0) {
|
||||
pastMessages.forEach(m => messages.push({ role: m.role, content: m.content }));
|
||||
} else {
|
||||
messages.push({ role: 'user', content });
|
||||
}
|
||||
|
||||
const aiContent = await callAiChat(messages);
|
||||
startSse(res);
|
||||
const aiContent = await streamOllama(messages, (delta) => sseWrite(res, { delta }), { numPredict: 800 });
|
||||
|
||||
const { data: aiMessage, error: aiMsgError } = await supabase
|
||||
.from('chat_messages')
|
||||
.insert([{ case_id: caseId, user_id: userId, role: 'assistant', content: aiContent }])
|
||||
.select().single();
|
||||
if (aiMsgError) throw aiMsgError;
|
||||
if (aiMsgError) console.error('Save AI Message Error:', aiMsgError);
|
||||
|
||||
// Bu dosyanın ilk mesajıysa (kullanıcının bu isteklen önce hiç mesajı yoktu) VE
|
||||
// bu gerçek bir dava dosyası değil de dosyasız bir sohbetse, ilk mesajdan kısa
|
||||
@@ -163,10 +163,16 @@ export const chatMessage = async (req: AuthenticatedRequest, res: Response) => {
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ message: aiMessage, newTitle });
|
||||
sseWrite(res, { done: true, newTitle, messageId: aiMessage?.id || null });
|
||||
res.end();
|
||||
} catch (error: any) {
|
||||
console.error('Chat Error:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal Server Error' });
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: error.message || 'Internal Server Error' });
|
||||
} else {
|
||||
sseWrite(res, { error: error.message || 'Internal Server Error' });
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -174,16 +180,17 @@ export const chatMessage = async (req: AuthenticatedRequest, res: Response) => {
|
||||
// tabloları cases ile hiçbir ilişkiye sahip değil (bkz. sql/9_chat_threads.sql).
|
||||
// Thread oluşturma/silme/geçmiş okuma doğrudan frontend'den Supabase ile yapılıyor
|
||||
// (events/clients/drafts ile aynı desen); burada sadece AI çağrısı + mesaj kaydı var.
|
||||
// chatMessage ile aynı SSE akış deseni kullanılır.
|
||||
export const sendThreadMessage = async (req: AuthenticatedRequest, res: Response) => {
|
||||
const { threadId } = req.params;
|
||||
const { content } = req.body;
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!content || !threadId) {
|
||||
return res.status(400).json({ error: 'Missing threadId or content' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { threadId } = req.params;
|
||||
const { content } = req.body;
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!content || !threadId) {
|
||||
return res.status(400).json({ error: 'Missing threadId or content' });
|
||||
}
|
||||
|
||||
const { data: thread } = await supabase
|
||||
.from('chat_threads')
|
||||
.select('id, user_id')
|
||||
@@ -206,20 +213,21 @@ export const sendThreadMessage = async (req: AuthenticatedRequest, res: Response
|
||||
.limit(20);
|
||||
|
||||
const generalSystemPrompt = 'Sen bir hukuk asistanısın. Belirli bir dava dosyasına bağlı olmayan genel hukuki soruları cevaplıyorsun. Emin olmadığın noktalarda bunu açıkça belirt, uydurma bilgi verme.';
|
||||
const messages: any[] = [{ role: 'system', content: generalSystemPrompt }];
|
||||
const messages: ChatMsg[] = [{ role: 'system', content: generalSystemPrompt }];
|
||||
if (pastMessages && pastMessages.length > 0) {
|
||||
pastMessages.forEach(m => messages.push({ role: m.role, content: m.content }));
|
||||
} else {
|
||||
messages.push({ role: 'user', content });
|
||||
}
|
||||
|
||||
const aiContent = await callAiChat(messages);
|
||||
startSse(res);
|
||||
const aiContent = await streamOllama(messages, (delta) => sseWrite(res, { delta }), { numPredict: 800 });
|
||||
|
||||
const { data: aiMessage, error: aiMsgError } = await supabase
|
||||
.from('thread_messages')
|
||||
.insert([{ thread_id: threadId, user_id: userId, role: 'assistant', content: aiContent }])
|
||||
.select().single();
|
||||
if (aiMsgError) throw aiMsgError;
|
||||
if (aiMsgError) console.error('Save Thread Message Error:', aiMsgError);
|
||||
|
||||
let newTitle: string | null = null;
|
||||
const isFirstMessage = !pastMessages || pastMessages.length <= 1;
|
||||
@@ -237,9 +245,15 @@ export const sendThreadMessage = async (req: AuthenticatedRequest, res: Response
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ message: aiMessage, newTitle });
|
||||
sseWrite(res, { done: true, newTitle, messageId: aiMessage?.id || null });
|
||||
res.end();
|
||||
} catch (error: any) {
|
||||
console.error('Send Thread Message Error:', error);
|
||||
res.status(500).json({ error: error.message || 'Internal Server Error' });
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: error.message || 'Internal Server Error' });
|
||||
} else {
|
||||
sseWrite(res, { error: error.message || 'Internal Server Error' });
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ export const generateDraft = async (req: AuthenticatedRequest, res: Response) =>
|
||||
userContent += `\n---\nBÜRO ŞABLONU "${template.name}" (SADECE dil/format/üslup referansı — içeriğini kopyalama):\n${template.extracted_text.slice(0, 6000)}\n`;
|
||||
}
|
||||
|
||||
const { aiContent, aiModel } = await callAiModelRaw(DRAFT_SYSTEM_PROMPT, userContent);
|
||||
const { aiContent, aiModel } = await callAiModelRaw(DRAFT_SYSTEM_PROMPT, userContent, 2500);
|
||||
|
||||
// Taslağı kalıcı olarak kaydediyoruz — hem "Dilekçelerim" listesinde hem ilgili
|
||||
// dava dosyasının kendi "Dilekçeler" bölümünde görünsün, sayfadan çıkınca kaybolmasın.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { AuthenticatedRequest } from '../middleware/auth';
|
||||
import { getAiConfig } from '../lib/aiConfig';
|
||||
import { callOllama } from '../lib/aiClient';
|
||||
|
||||
export const searchPrecedents = async (req: Request, res: Response) => {
|
||||
try {
|
||||
@@ -21,34 +21,19 @@ SADECE anahtar kelimeleri aralarına boşluk koyarak yaz. Noktalama işareti, a
|
||||
|
||||
// AI ile sorgu optimizasyonu isteğe bağlı bir iyileştirme — config eksikse veya
|
||||
// istek başarısız olursa arama yine de optimize edilmemiş phrase ile devam eder.
|
||||
// Çıktı birkaç kelimelik bir anahtar kelime listesi olduğu için num_predict düşük
|
||||
// tutuluyor — hem daha hızlı yanıt hem modelin gereksiz uzatmasını önler.
|
||||
try {
|
||||
const { runpodUrl, aiModel } = getAiConfig();
|
||||
const aiResponse = 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: phrase }
|
||||
],
|
||||
think: false,
|
||||
stream: false,
|
||||
}),
|
||||
});
|
||||
|
||||
if (aiResponse.ok) {
|
||||
const data = await aiResponse.json();
|
||||
const aiContent = data.message?.content || (data.choices && data.choices[0]?.message?.content);
|
||||
if (aiContent) {
|
||||
optimizedPhrase = aiContent.trim().replace(/["'\n]/g, '');
|
||||
console.log(`[AI Search] Orijinal: "${phrase}" -> Optimize: "${optimizedPhrase}"`);
|
||||
}
|
||||
} else {
|
||||
console.error('AI Query Optimization API returned status:', aiResponse.status);
|
||||
const aiContent = await callOllama([
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: phrase },
|
||||
], { numPredict: 40, timeoutMs: 20_000 });
|
||||
if (aiContent) {
|
||||
optimizedPhrase = aiContent.trim().replace(/["'\n]/g, '');
|
||||
console.log(`[AI Search] Orijinal: "${phrase}" -> Optimize: "${optimizedPhrase}"`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('AI Query Optimization Fetch Error:', err);
|
||||
console.error('AI Query Optimization Error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,28 +98,10 @@ export const analyzePrecedents = async (req: AuthenticatedRequest, res: Response
|
||||
const systemPrompt = 'Sen uzman bir Türk avukatısın. Kullanıcının anlattığı hukuki durumu, aşağıda listelenen emsal karar arama sonuçları ışığında kısaca değerlendir. Hangi kararların duruma daha yakın olabileceğini belirt ve olası bir hukuki değerlendirme sun. Emin olmadığın noktalarda bunu açıkça belirt, uydurma bilgi verme. Cevabın yaklaşık 150-250 kelime olsun.';
|
||||
const userPrompt = `DURUM:\n${String(situation).slice(0, 3000)}\n\nBULUNAN KARARLAR:\n${listText}`;
|
||||
|
||||
const { runpodUrl, aiModel } = getAiConfig();
|
||||
|
||||
const response = await fetch(`${runpodUrl}/api/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
model: aiModel,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt },
|
||||
],
|
||||
think: false,
|
||||
stream: false,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`AI Model request failed: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const analysis = data.message?.content || 'Analiz üretilemedi.';
|
||||
const analysis = await callOllama([
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: userPrompt },
|
||||
], { numPredict: 500 }) || 'Analiz üretilemedi.';
|
||||
res.json({ analysis });
|
||||
} catch (error: any) {
|
||||
console.error('Analyze Precedents Error:', error);
|
||||
|
||||
Reference in New Issue
Block a user