Spaces:
Running on Zero
Running on Zero
| 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 |