Créer un bot de support IA pour votre boutique en ligne
Bot de support en Python : code source
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."}