sk75's picture
Upload 8 files
1b38924 verified
Raw
History Blame Contribute Delete
9.74 kB
import json
import os
from typing import List, Dict, Any, Tuple
from openai import OpenAI
import config
import tools
EXECUTION_LOGS: List[Dict[str, Any]] = []
import re
SYSTEM_PROMPT = """Sen uzman bir Akademik Araştırma ve Makale Yönetim Asistanısın.
Sistemde kullanabileceğin fonksiyonlar (tool call) tanımlanmıştır:
1. `search_arxiv_papers`: ArXiv platformunda makale arar.
2. `search_openalex`: OpenAlex platformunda atıf sayıları ile makale arar.
3. `save_paper_to_library`: Bulunan veya belirtilen bir makaleyi veritabanındaki kişisel kütüphaneye kaydeder.
4. `get_saved_library`: Kullanıcının kayıtlı makalelerini ve okuma durumlarını listeler.
5. `update_paper_status_or_note`: Kayıtlı makalelerin okuma durumunu ('unread', 'reading', 'completed') veya kişisel notlarını günceller.
6. `delete_paper`: Kullanıcının girdiği paper idsine veya başlığına sahip makaleyi veritabanından siler.
7. `clear_library`: Veritabanındaki tüm makaleleri kalıcı olarak siler ve kütüphaneyi temizler.
ÖNEMLİ KURALLAR (MULTI-STEP TOOL CALL & HALÜSİNASYON ENGELLEME):
- Kullanıcı bir makaleyi kütüphaneye eklemek/kaydetmek istediğinde (örn: 'LSTM makalesini kütüphaneye ekle'):
1. ÖNCE `search_arxiv_papers` veya `search_openalex` fonksiyonu ile makaleleri ara.
2. Dönen sonuçlar arasından ilgili makale(ler) için `save_paper_to_library` fonksiyonunu çağırarak kütüphaneye kaydet.
3. Kayıt gerçekleştikten sonra kullanıcıya kaydedilen makalelerin başlık ve detaylarını özetle.
- KESİNLİKLE `<think>` veya iç düşünce bloklarını yanıtlarına koyma. Doğrudan net yanıt ver.
- KESİNLİKLE veritabanında veya API aramasında olmayan uydurma bir makale, yazar veya istatistik SÖYLEME.
- Tüm yanıtların SADECE ve SADECE en son fonksiyonlardan (tool call) dönen gerçek veriye dayanmalıdır.
- EĞER `get_saved_library` çağrıldığında dönen kütüphane boşsa (`library: []` veya `saved_count: 0`), KESİNLİKLE sohbet geçmişindeki eski silinmiş makaleleri kütüphanedeymiş gibi LISTELEME! Kütüphanenin boş olduğunu açıkça söyle.
- Türkçe, kibar ve akademik bir dil kullan.
"""
def log_execution(step_type: str, name: str, input_data: Any, output_data: Any):
"""Arka planda gerçekleşen Tool-Call işlemlerini loglar."""
log_entry = {
"timestamp": json.loads(json.dumps(str(os.times()))),
"type": step_type,
"name": name,
"input": input_data,
"output": output_data
}
EXECUTION_LOGS.append(log_entry)
def get_client_and_model(custom_api_key: str = "", custom_provider: str = "", custom_model: str = "") -> Tuple[OpenAI, str]:
"""Seçilen konfigürasyona göre OpenAI-uyumlu client ve model ismini döndürür."""
groq_key = custom_api_key or config.GROQ_API_KEY
openrouter_key = custom_api_key or config.OPENROUTER_API_KEY
openai_key = custom_api_key or config.OPENAI_API_KEY
if custom_provider == "Groq" or (groq_key and not custom_provider):
client = OpenAI(
base_url="https://api.groq.com/openai/v1",
api_key=groq_key or "dummy_key"
)
model = custom_model or "llama-3.3-70b-versatile"
elif custom_provider == "OpenRouter" or (openrouter_key and not custom_provider):
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=openrouter_key or "dummy_key"
)
model = custom_model or "meta-llama/llama-3.1-8b-instruct"
elif custom_provider == "Ollama (Remote/Local)":
client = OpenAI(
base_url=config.OLLAMA_BASE_URL,
api_key="ollama"
)
model = custom_model or "llama3.2"
elif custom_provider == "OpenAI" or openai_key:
client = OpenAI(
api_key=openai_key or "dummy_key"
)
model = custom_model or "gpt-4o-mini"
else:
client = OpenAI(
base_url="https://api.groq.com/openai/v1" if groq_key else config.OLLAMA_BASE_URL,
api_key=groq_key or "ollama"
)
model = custom_model or config.DEFAULT_MODEL
return client, model
def process_user_query(
user_message: str,
chat_history: List[Dict[str, str]],
api_key: str = "",
provider: str = config.DEFAULT_PROVIDER,
model_name: str = config.DEFAULT_MODEL
) -> Tuple[str, List[Dict[str, str]]]:
"""
Kullanıcı mesajını alır, LLM veya Intent Engine ile tool call süreçlerini yürütür.
Çok adımlı (multi-step) tool çağrılarını desteklemek için döngü içerir.
"""
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
for msg in chat_history:
messages.append(msg)
messages.append({"role": "user", "content": user_message})
try:
client, target_model = get_client_and_model(api_key, provider, model_name)
max_iterations = 5
iteration = 0
final_content = ""
while iteration < max_iterations:
iteration += 1
response = client.chat.completions.create(
model=target_model,
messages=messages,
tools=tools.TOOLS_SCHEMA,
tool_choice="auto"
)
response_message = response.choices[0].message
content_text = response_message.content or ""
if response_message.tool_calls:
messages.append(response_message)
for tool_call in response_message.tool_calls:
function_name = tool_call.function.name
try:
raw_args = tool_call.function.arguments
function_args = json.loads(raw_args) if raw_args else {}
except Exception:
function_args = {}
if not isinstance(function_args, dict):
function_args = {}
else:
function_args = {k: v for k, v in function_args.items() if v is not None}
log_execution("TOOL_CALL_TRIGGERED", function_name, function_args, None)
tool_response = tools.execute_tool(function_name, function_args)
log_execution("TOOL_CALL_RESULT", function_name, function_args, tool_response)
messages.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": json.dumps(tool_response, ensure_ascii=False)
})
# Multi-step: Döngüye devam et ki LLM sonuçları görüp ikinci bir tool (örn: save_paper) çağırabilsin
continue
# 2. Metin Tabanı <function> veya <tool_call> Taglerini Yakalama
elif "<function>" in content_text or "<tool_call>" in content_text:
text_tool_matches = re.findall(
r'<(?:function|tool_call)>([^<]+)</(?:function|tool_call)>\s*(\{.*?\})',
content_text,
flags=re.DOTALL
)
if text_tool_matches:
messages.append({"role": "assistant", "content": content_text})
executed_results = []
for func_name, raw_json in text_tool_matches:
func_name = func_name.strip()
try:
func_args = json.loads(raw_json.strip())
except Exception:
func_args = {}
if isinstance(func_args, dict):
func_args = {k: v for k, v in func_args.items() if v is not None}
else:
func_args = {}
log_execution("TOOL_CALL_TRIGGERED (TEXT_TAG)", func_name, func_args, None)
t_res = tools.execute_tool(func_name, func_args)
log_execution("TOOL_CALL_RESULT (TEXT_TAG)", func_name, func_args, t_res)
executed_results.append(f"Tool '{func_name}' sonucu: {json.dumps(t_res, ensure_ascii=False)}")
messages.append({
"role": "user",
"content": "Çalıştırılan fonksiyon sonuçları:\n" + "\n".join(executed_results)
})
continue
else:
final_content = content_text
break
else:
final_content = content_text
break
if not final_content and len(messages) > 0:
last_msg = messages[-1]
if isinstance(last_msg, dict) and last_msg.get("role") == "tool":
final_content = "İşlem başarıyla tamamlandı."
else:
final_content = content_text or "İşlem tamamlandı."
# Temizlik: <think> ve etiket artıkları
if final_content:
final_content = re.sub(r'<think>.*?</think>', '', final_content, flags=re.DOTALL)
final_content = re.sub(r'<(?:function|tool_call)>.*?</(?:function|tool_call)>\s*\{.*?\}', '', final_content, flags=re.DOTALL)
final_content = final_content.strip()
return final_content or "İşlem tamamlandı.", EXECUTION_LOGS
except Exception as e:
error_msg = f"API Çağrı Hatası ({str(e)})."
log_execution("ERROR", "API_FAIL", {"error": str(error_msg)}, None)
return f"⚠️ {error_msg}", EXECUTION_LOGS