Créer un bot de support IA pour votre boutique en ligne

Bot de support en Python : code source

Fichiers : 2988,4 KiBTélécharger le ZIP

store_assistant/tools.py

6,8 KiBBrutTélécharger
Python
1"""Tools the model can call, and the code that runs them against the store."""23from __future__ import annotations45import json6import logging7from collections.abc import Callable8from dataclasses import dataclass, field9from typing import Any1011from .store import Store1213logger = logging.getLogger(__name__)1415CATEGORIES = ["t-shirts", "hoodies", "jeans", "sneakers", "jackets", "accessories"]1617TOOL_SCHEMAS: list[dict[str, Any]] = [18    {19        "type": "function",20        "function": {21            "name": "get_order_status",22            "description": (23                "Look up an existing order. Requires the order number (format NS-12345) and the last 4 digits "24                "of the phone number used for the order. Ask the customer for both before calling."25            ),26            "parameters": {27                "type": "object",28                "properties": {29                    "order_id": {"type": "string", "description": "Order number, e.g. NS-10241"},30                    "phone_last4": {"type": "string", "description": "Last 4 digits of the customer's phone number"},31                },32                "required": ["order_id", "phone_last4"],33                "additionalProperties": False,34            },35        },36    },37    {38        "type": "function",39        "function": {40            "name": "search_products",41            "description": (42                "Search the catalog for products, prices, colors and sizes in stock. "43                f"Write the query as short English keywords (product type, category or color). Categories: {', '.join(CATEGORIES)}."44            ),45            "parameters": {46                "type": "object",47                "properties": {48                    "query": {"type": "string", "description": "English keywords, e.g. 'sneakers', 'black hoodie', 'jeans'"},49                    "size": {"type": "string", "description": "Optional size to check, e.g. 'L', '42', '32'"},50                },51                "required": ["query"],52                "additionalProperties": False,53            },54        },55    },56    {57        "type": "function",58        "function": {59            "name": "get_delivery_fee",60            "description": "Get the delivery fee and estimated delivery time to an Algerian wilaya, by name or number.",61            "parameters": {62                "type": "object",63                "properties": {64                    "wilaya": {"type": "string", "description": "Wilaya name in any language, or its number, e.g. 'Oran', 'وهران', '31'"},65                },66                "required": ["wilaya"],67                "additionalProperties": False,68            },69        },70    },71    {72        "type": "function",73        "function": {74            "name": "get_store_policies",75            "description": "Get the store's return and exchange policy, support hours and contact rules.",76            "parameters": {"type": "object", "properties": {}, "additionalProperties": False},77        },78    },79    {80        "type": "function",81        "function": {82            "name": "request_human_agent",83            "description": (84                "Hand the conversation to a human agent. Use it when the customer asks for a person, has a complaint, "85                "wants to change or cancel an order, or when you cannot solve the request with the other tools."86            ),87            "parameters": {88                "type": "object",89                "properties": {90                    "reason": {"type": "string", "description": "One-sentence summary of what the customer needs, in English"},91                },92                "required": ["reason"],93                "additionalProperties": False,94            },95        },96    },97]9899100@dataclass101class ToolContext:102    """Who is talking to the bot, so tools can act on behalf of the right customer."""103104    chat_id: int105    username: str | None = None106    tickets: list[int] = field(default_factory=list)107108109class ToolExecutor:110    def __init__(self, store: Store) -> None:111        self._store = store112        self._handlers: dict[str, Callable[[dict[str, Any], ToolContext], dict[str, Any]]] = {113            "get_order_status": self._get_order_status,114            "search_products": self._search_products,115            "get_delivery_fee": self._get_delivery_fee,116            "get_store_policies": self._get_store_policies,117            "request_human_agent": self._request_human_agent,118        }119120    def run(self, name: str, raw_arguments: str | None, ctx: ToolContext) -> str:121        """Run one tool call and return a JSON string for the model. Never raises."""122        handler = self._handlers.get(name)123        if handler is None:124            return json.dumps({"error": f"unknown tool '{name}'"})125        try:126            args = json.loads(raw_arguments) if raw_arguments else {}127            if not isinstance(args, dict):128                raise ValueError("arguments must be a JSON object")129        except (json.JSONDecodeError, ValueError) as exc:130            return json.dumps({"error": f"invalid arguments: {exc}"})131        try:132            result = handler(args, ctx)133        except Exception:134            logger.exception("tool %s failed", name)135            return json.dumps({"error": "internal error while running the tool"})136        return json.dumps(result, ensure_ascii=False)137138    def _get_order_status(self, args: dict[str, Any], ctx: ToolContext) -> dict[str, Any]:139        order = self._store.order_status(str(args.get("order_id", "")), str(args.get("phone_last4", "")))140        if order is None:141            return {142                "found": False,143                "message": "No order matches this number and these phone digits. Ask the customer to check both.",144            }145        return {"found": True, "order": order}146147    def _search_products(self, args: dict[str, Any], ctx: ToolContext) -> dict[str, Any]:148        size = args.get("size")149        products = self._store.search_products(str(args.get("query", "")), str(size) if size else None)150        return {"count": len(products), "products": products, "currency": "DZD"}151152    def _get_delivery_fee(self, args: dict[str, Any], ctx: ToolContext) -> dict[str, Any]:153        fee = self._store.delivery_fee(str(args.get("wilaya", "")))154        if fee is None:155            return {"covered": False, "covered_wilayas": self._store.covered_wilayas()}156        return {"covered": True, **fee, "currency": "DZD"}157158    def _get_store_policies(self, args: dict[str, Any], ctx: ToolContext) -> dict[str, Any]:159        return self._store.store_info()160161    def _request_human_agent(self, args: dict[str, Any], ctx: ToolContext) -> dict[str, Any]:162        reason = str(args.get("reason", "")).strip() or "Customer asked for a human agent"163        ticket_id = self._store.create_ticket(ctx.chat_id, ctx.username, reason)164        ctx.tickets.append(ticket_id)165        return {"ticket_id": ticket_id, "message": "A human agent has been notified and will reply in this chat."}