YusufSimsek's picture
Add bookstore tool-calling assistant project
58f6928 verified
Raw
History Blame Contribute Delete
10.7 kB
import unicodedata
from typing import Any
from data.database import get_connection, initialize_database
def normalize_text(value: str) -> str:
"""
Türkçe karakter ve büyük-küçük harf farklılıklarını azaltarak
arama işlemlerini daha dayanıklı hâle getirir.
Örnek:
"KÖRLÜK" -> "korluk"
"Suç ve Ceza" -> "suc ve ceza"
"""
value = value.strip().casefold()
replacements = {
"ı": "i",
"ğ": "g",
"ü": "u",
"ş": "s",
"ö": "o",
"ç": "c",
}
for source, target in replacements.items():
value = value.replace(source, target)
return "".join(
character
for character in unicodedata.normalize("NFKD", value)
if not unicodedata.combining(character)
)
def serialize_book(book: Any) -> dict[str, Any]:
"""SQLite kitap satırını JSON uyumlu sözlüğe dönüştürür."""
return {
"book_id": book["id"],
"title": book["title"],
"author": book["author"],
"category": book["category"],
"price": round(float(book["price"]), 2),
"stock": int(book["stock"]),
"available": int(book["stock"]) > 0,
}
def search_books(
query: str | None = None,
author: str | None = None,
category: str | None = None,
in_stock_only: bool = False,
limit: int = 10,
) -> dict[str, Any]:
"""
Kitapları başlık, yazar veya kategoriye göre arar.
Model, kitap bilgilerini kendisi üretmek yerine bu fonksiyondan
dönen gerçek verileri kullanmalıdır.
"""
initialize_database()
if limit < 1:
return {
"success": False,
"error": "limit değeri en az 1 olmalıdır.",
}
limit = min(limit, 20)
normalized_query = normalize_text(query) if query else None
normalized_author = normalize_text(author) if author else None
normalized_category = normalize_text(category) if category else None
with get_connection() as connection:
rows = connection.execute(
"""
SELECT
id,
title,
author,
category,
price,
stock
FROM books
ORDER BY title
"""
).fetchall()
matched_books: list[dict[str, Any]] = []
for row in rows:
normalized_title_value = normalize_text(row["title"])
normalized_author_value = normalize_text(row["author"])
normalized_category_value = normalize_text(row["category"])
if normalized_query:
query_matches = (
normalized_query in normalized_title_value
or normalized_query in normalized_author_value
or normalized_query in normalized_category_value
)
if not query_matches:
continue
if (
normalized_author
and normalized_author not in normalized_author_value
):
continue
if (
normalized_category
and normalized_category not in normalized_category_value
):
continue
if in_stock_only and row["stock"] <= 0:
continue
matched_books.append(serialize_book(row))
if len(matched_books) >= limit:
break
return {
"success": True,
"count": len(matched_books),
"filters": {
"query": query,
"author": author,
"category": category,
"in_stock_only": in_stock_only,
"limit": limit,
},
"books": matched_books,
"message": (
f"{len(matched_books)} kitap bulundu."
if matched_books
else "Arama kriterlerine uygun kitap bulunamadı."
),
}
def create_order(
book_id: int,
quantity: int,
customer_name: str,
) -> dict[str, Any]:
"""
Sipariş oluşturur ve kitap stoğunu düşürür.
Sipariş kaydı ile stok güncellemesi aynı transaction içinde yapılır.
Böylece işlemlerden biri başarısız olursa veritabanı yarım kalmaz.
"""
initialize_database()
customer_name = customer_name.strip()
if not customer_name:
return {
"success": False,
"error": "Müşteri adı boş bırakılamaz.",
}
if not isinstance(book_id, int) or isinstance(book_id, bool):
return {
"success": False,
"error": "book_id tam sayı olmalıdır.",
}
if not isinstance(quantity, int) or isinstance(quantity, bool):
return {
"success": False,
"error": "quantity tam sayı olmalıdır.",
}
if quantity < 1:
return {
"success": False,
"error": "Sipariş miktarı en az 1 olmalıdır.",
}
connection = get_connection()
try:
# Aynı anda gelen iki siparişin aynı stoğu kullanmasını önlemek
# için yazma kilidiyle transaction başlatılır.
connection.execute("BEGIN IMMEDIATE")
book = connection.execute(
"""
SELECT
id,
title,
author,
category,
price,
stock
FROM books
WHERE id = ?
""",
(book_id,),
).fetchone()
if book is None:
connection.rollback()
return {
"success": False,
"error": "Belirtilen kimliğe sahip kitap bulunamadı.",
"book_id": book_id,
}
current_stock = int(book["stock"])
if current_stock < quantity:
connection.rollback()
return {
"success": False,
"error": "Yeterli stok bulunmuyor.",
"book_id": book_id,
"title": book["title"],
"requested_quantity": quantity,
"available_stock": current_stock,
}
unit_price = round(float(book["price"]), 2)
total_price = round(unit_price * quantity, 2)
new_stock = current_stock - quantity
cursor = connection.execute(
"""
INSERT INTO orders (
customer_name,
book_id,
quantity,
unit_price,
total_price,
status
)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
customer_name,
book_id,
quantity,
unit_price,
total_price,
"Hazırlanıyor",
),
)
order_id = cursor.lastrowid
connection.execute(
"""
UPDATE books
SET stock = ?
WHERE id = ?
""",
(
new_stock,
book_id,
),
)
connection.commit()
return {
"success": True,
"message": "Sipariş başarıyla oluşturuldu.",
"order": {
"order_id": order_id,
"customer_name": customer_name,
"book_id": book_id,
"title": book["title"],
"author": book["author"],
"quantity": quantity,
"unit_price": unit_price,
"total_price": total_price,
"status": "Hazırlanıyor",
},
"stock_update": {
"previous_stock": current_stock,
"new_stock": new_stock,
},
}
except Exception as error:
connection.rollback()
return {
"success": False,
"error": "Sipariş oluşturulurken veritabanı hatası oluştu.",
"detail": str(error),
}
finally:
connection.close()
def get_order_status(order_id: int) -> dict[str, Any]:
"""Sipariş numarasına göre sipariş bilgilerini getirir."""
initialize_database()
if not isinstance(order_id, int) or isinstance(order_id, bool):
return {
"success": False,
"error": "order_id tam sayı olmalıdır.",
}
with get_connection() as connection:
order = connection.execute(
"""
SELECT
orders.id AS order_id,
orders.customer_name,
orders.quantity,
orders.unit_price,
orders.total_price,
orders.status,
orders.created_at,
books.id AS book_id,
books.title,
books.author
FROM orders
INNER JOIN books
ON books.id = orders.book_id
WHERE orders.id = ?
""",
(order_id,),
).fetchone()
if order is None:
return {
"success": False,
"error": "Sipariş bulunamadı.",
"order_id": order_id,
}
return {
"success": True,
"order": {
"order_id": order["order_id"],
"customer_name": order["customer_name"],
"book_id": order["book_id"],
"title": order["title"],
"author": order["author"],
"quantity": order["quantity"],
"unit_price": round(float(order["unit_price"]), 2),
"total_price": round(float(order["total_price"]), 2),
"status": order["status"],
"created_at": order["created_at"],
},
}
TOOL_FUNCTIONS = {
"search_books": search_books,
"create_order": create_order,
"get_order_status": get_order_status,
}
def execute_tool(
tool_name: str,
arguments: dict[str, Any],
) -> dict[str, Any]:
"""
Model tarafından seçilen tool adını ilgili Python fonksiyonuna yönlendirir.
"""
tool_function = TOOL_FUNCTIONS.get(tool_name)
if tool_function is None:
return {
"success": False,
"error": f"Desteklenmeyen tool: {tool_name}",
}
try:
return tool_function(**arguments)
except TypeError as error:
return {
"success": False,
"error": "Tool parametreleri geçersiz veya eksik.",
"detail": str(error),
}
except Exception as error:
return {
"success": False,
"error": "Tool çalıştırılırken beklenmeyen hata oluştu.",
"detail": str(error),
}