| import json |
| from typing import Any |
|
|
| from llm_client import LLMClient |
| from prompts import SYSTEM_PROMPT |
| from tool_schemas import get_tool_schemas |
| from tools import execute_tool |
|
|
|
|
| class BookstoreAgent: |
| """ |
| Kullanıcı mesajını modele gönderir, tool çağrılarını çalıştırır |
| ve veritabanı sonucuna dayalı nihai cevabı üretir. |
| """ |
|
|
| def __init__( |
| self, |
| *, |
| model_id: str | None = None, |
| max_tool_iterations: int = 6, |
| ) -> None: |
| self.llm_client = LLMClient(model_id=model_id) |
| self.tool_schemas = get_tool_schemas(strict=False) |
| self.max_tool_iterations = max_tool_iterations |
|
|
| @staticmethod |
| def parse_tool_arguments( |
| raw_arguments: Any, |
| ) -> dict[str, Any]: |
| """ |
| Farklı inference sağlayıcılarından gelebilecek tool |
| argümanlarını güvenli biçimde sözlüğe dönüştürür. |
| """ |
|
|
| if isinstance(raw_arguments, dict): |
| return raw_arguments |
|
|
| if isinstance(raw_arguments, str): |
| try: |
| parsed_arguments = json.loads(raw_arguments) |
| except json.JSONDecodeError as error: |
| raise ValueError( |
| "Model geçerli JSON tool parametresi üretmedi." |
| ) from error |
|
|
| if not isinstance(parsed_arguments, dict): |
| raise ValueError( |
| "Tool parametrelerinin JSON nesnesi olması gerekiyor." |
| ) |
|
|
| return parsed_arguments |
|
|
| raise ValueError( |
| "Tool parametreleri desteklenmeyen bir biçimde geldi." |
| ) |
|
|
| @staticmethod |
| def prepare_history( |
| history: list[dict[str, str]] | None, |
| ) -> list[dict[str, str]]: |
| """ |
| Önceki konuşmadan yalnızca user ve assistant mesajlarını alır. |
| """ |
|
|
| prepared_messages: list[dict[str, str]] = [] |
|
|
| if not history: |
| return prepared_messages |
|
|
| |
| |
| for message in history[-12:]: |
| role = message.get("role") |
| content = message.get("content", "") |
|
|
| if role not in {"user", "assistant"}: |
| continue |
|
|
| if not isinstance(content, str): |
| continue |
|
|
| prepared_messages.append( |
| { |
| "role": role, |
| "content": content, |
| } |
| ) |
|
|
| return prepared_messages |
|
|
| def run( |
| self, |
| user_message: str, |
| history: list[dict[str, str]] | None = None, |
| ) -> dict[str, Any]: |
| """ |
| Tek bir kullanıcı isteğini uçtan uca işler. |
| |
| Dönüş: |
| { |
| "success": bool, |
| "answer": str, |
| "tool_logs": [...] |
| } |
| """ |
|
|
| user_message = user_message.strip() |
|
|
| if not user_message: |
| return { |
| "success": False, |
| "answer": "Lütfen bir mesaj yazın.", |
| "tool_logs": [], |
| } |
|
|
| messages: list[Any] = [ |
| { |
| "role": "system", |
| "content": SYSTEM_PROMPT, |
| } |
| ] |
|
|
| messages.extend(self.prepare_history(history)) |
|
|
| messages.append( |
| { |
| "role": "user", |
| "content": user_message, |
| } |
| ) |
|
|
| tool_logs: list[dict[str, Any]] = [] |
|
|
| |
| |
| authorized_book_ids: set[int] = set() |
|
|
| |
| |
| order_created_in_this_turn = False |
|
|
| for iteration in range(1, self.max_tool_iterations + 1): |
| response = self.llm_client.create_chat_completion( |
| messages, |
| tools=self.tool_schemas, |
| tool_choice="auto", |
| ) |
|
|
| if not response.choices: |
| return { |
| "success": False, |
| "answer": "Model boş bir cevap döndürdü.", |
| "tool_logs": tool_logs, |
| } |
|
|
| response_message = response.choices[0].message |
| tool_calls = getattr( |
| response_message, |
| "tool_calls", |
| None, |
| ) |
|
|
| |
| if not tool_calls: |
| answer = getattr( |
| response_message, |
| "content", |
| None, |
| ) |
|
|
| if not answer: |
| answer = ( |
| "İşlem tamamlandı ancak model metin cevabı " |
| "oluşturmadı." |
| ) |
|
|
| return { |
| "success": True, |
| "answer": str(answer).strip(), |
| "tool_logs": tool_logs, |
| "iterations": iteration, |
| } |
|
|
| |
| |
| messages.append(response_message) |
|
|
| for tool_call in tool_calls: |
| tool_name = tool_call.function.name |
| raw_arguments = tool_call.function.arguments |
|
|
| try: |
| arguments = self.parse_tool_arguments( |
| raw_arguments |
| ) |
| except ValueError as error: |
| arguments = {} |
|
|
| tool_result = { |
| "success": False, |
| "error": str(error), |
| } |
| else: |
| |
| if tool_name == "create_order": |
| requested_book_id = arguments.get("book_id") |
|
|
| if order_created_in_this_turn: |
| tool_result = { |
| "success": False, |
| "error": ( |
| "Bu kullanıcı isteği için zaten bir " |
| "sipariş oluşturuldu. İkinci sipariş " |
| "oluşturulmadı." |
| ), |
| } |
|
|
| elif requested_book_id not in authorized_book_ids: |
| tool_result = { |
| "success": False, |
| "error": ( |
| "create_order çağrısından önce " |
| "search_books kullanılmalı ve book_id " |
| "o tool sonucundan alınmalıdır." |
| ), |
| "requested_book_id": requested_book_id, |
| } |
|
|
| else: |
| tool_result = execute_tool( |
| tool_name, |
| arguments, |
| ) |
|
|
| else: |
| tool_result = execute_tool( |
| tool_name, |
| arguments, |
| ) |
|
|
| |
| if ( |
| tool_name == "search_books" |
| and tool_result.get("success") is True |
| ): |
| for book in tool_result.get("books", []): |
| book_id = book.get("book_id") |
|
|
| if isinstance(book_id, int): |
| authorized_book_ids.add(book_id) |
|
|
| if ( |
| tool_name == "create_order" |
| and tool_result.get("success") is True |
| ): |
| order_created_in_this_turn = True |
|
|
| log_entry = { |
| "iteration": iteration, |
| "tool_call_id": tool_call.id, |
| "tool_name": tool_name, |
| "arguments": arguments, |
| "result": tool_result, |
| } |
|
|
| tool_logs.append(log_entry) |
|
|
| print("\n" + "=" * 80) |
| print(f"TOOL CALL: {tool_name}") |
| print("=" * 80) |
| print( |
| "Arguments:", |
| json.dumps( |
| arguments, |
| ensure_ascii=False, |
| indent=2, |
| ), |
| ) |
| print( |
| "Result:", |
| json.dumps( |
| tool_result, |
| ensure_ascii=False, |
| indent=2, |
| ), |
| ) |
|
|
| |
| messages.append( |
| { |
| "tool_call_id": tool_call.id, |
| "role": "tool", |
| "name": tool_name, |
| "content": json.dumps( |
| tool_result, |
| ensure_ascii=False, |
| ), |
| } |
| ) |
|
|
| return { |
| "success": False, |
| "answer": ( |
| "İşlem güvenlik sınırına ulaştığı için durduruldu. " |
| "Lütfen isteğinizi daha açık biçimde tekrar yazın." |
| ), |
| "tool_logs": tool_logs, |
| "iterations": self.max_tool_iterations, |
| } |