CoolFace
Apppublic

codeBOKER/customer_service

sourceHugging Faceupdated 3mo agoView on Hugging Face
1likes
ai_service.py248 linesDownload Raw Back to root
1import re2import json3from config import pc, index, EMBED_MODEL, hf_client, PROMPT, HF_MODEL, TRANSFER_PROMPT4from database import db_manager5from transfers import (6    prepare_transfer,7    confirm_transfer,8    cancel_transfer,9    get_pending_transfer,10    get_account_balance,11    get_sender_account,12)13 14 15MODEL_NAME = HF_MODEL16BASE_PROMPT = PROMPT or "You are a helpful banking customer service assistant."17TRANSFER_PROMPT = TRANSFER_PROMPT or (18    "Current user telegram_id is {telegram_id}. "19    "The model must never choose, guess, extract, or override any telegram_id for tool calls. "20    "Always act only for the current authenticated user from server-side request context. "21    "If the user asks for another person's balance or provides another person's telegram ID, refuse and explain that you can only access the current user's own account. "22    "If the user asks for their balance, call check_account_balance. "23    "For money transfers, first collect the receiver account serial ID and the amount if it is missing. "24    "Then call prepare_money_transfer to fetch the receiver name and store the pending transfer. "25    "Show the receiver name back to the user and ask for explicit confirmation. "26    "Only call confirm_money_transfer after the user clearly agrees. "27    "If the user rejects the receiver or wants to stop, call cancel_money_transfer. "28    "Never claim a transfer is completed unless confirm_money_transfer returns success. "29    "If a tool result returns 'need_user_name': true, ask the user for their name and then call create_illusion_account with their name. "30    "If a tool result indicates an illusion account was created (contains 'is_illusion': true or mentions testing), inform the user that an illusion account with 2000 YER balance was created for testing purposes. "31    "If confirm_money_transfer returns a result with 'is_illusion': true, make sure to display the testing disclaimer message to the user."32)33 34 35def clean_ai_response(text: str) -> str:36    if not text:37        return ""38 39    text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)40    41    text = re.sub(r'<br\s*/?>', '\n', text, flags=re.IGNORECASE)42    text = re.sub(r'</?(p|div|span|section)[^>]*>', '\n', text, flags=re.IGNORECASE)43    44    text = re.sub(r'<[^>]+>', '', text)45 46    text = re.sub(r'^\|.*\|\s*$', '', text, flags=re.MULTILINE)47    text = re.sub(r'^[\s|:-]+$', '', text, flags=re.MULTILINE)48 49    text = re.sub(r'^#{1,6}\s*', '', text, flags=re.MULTILINE)50 51    text = re.sub(r'\n{3,}', '\n\n', text)52    53    return text.strip()54 55async def search_bank_knowledge(query: str):56    query_embedding = pc.inference.embed(57        model=EMBED_MODEL,58        inputs=[query],59        parameters={"input_type": "query"}60    )61    search_results = index.query(62        vector=query_embedding[0].values,63        top_k=3,64        include_metadata=True65    )66    return "\n".join([res.metadata['original_text'] for res in search_results.matches])67 68TOOLS = [69    {70        "type": "function",71        "function": {72            "name": "search_bank_knowledge",73            "description": "Use this tool to search the official Hadhramout Bank profile for accurate information about services, organizational structure, capital, and policies.",74            "parameters": {75                "type": "object",76                "properties": {77                    "query": {78                        "type": "string",79                        "description": "The search query (e.g., 'What is Hadhramout Bank capital?' or 'individual services')."80                    }81                },82                "required": ["query"]83            }84        }85    },86    {87        "type": "function",88        "function": {89            "name": "check_account_balance",90            "description": "Use this tool when the user wants to check their own account balance. The server identifies the current user from request context.",91            "parameters": {92                "type": "object",93                "properties": {},94                "required": []95            }96        }97    },98    {99        "type": "function",100        "function": {101            "name": "prepare_money_transfer",102            "description": "Use this tool when the user wants to transfer money. The server identifies the sender from request context, looks up the receiver by account serial ID, stores a pending transfer, and returns the receiver name for confirmation before any money is sent.",103            "parameters": {104                "type": "object",105                "properties": {106                    "receiver_serial_id": {107                        "type": "string",108                        "description": "The serial ID of the receiver account."109                    },110                    "amount": {111                        "type": "number",112                        "description": "Amount to transfer. Ask the user for it if missing."113                    }114                },115                "required": ["receiver_serial_id"]116            }117        }118    },119    {120        "type": "function",121        "function": {122            "name": "confirm_money_transfer",123            "description": "Use this tool only after the user confirms that the receiver account is correct and the transfer should proceed.",124            "parameters": {125                "type": "object",126                "properties": {},127                "required": []128            }129        }130    },131    {132        "type": "function",133        "function": {134            "name": "cancel_money_transfer",135            "description": "Use this tool when the user says the receiver is wrong or wants to stop a pending money transfer.",136            "parameters": {137                "type": "object",138                "properties": {},139                "required": []140            }141        }142    },143    {144        "type": "function",145        "function": {146            "name": "get_pending_money_transfer",147            "description": "Use this tool to inspect the current pending transfer for the current user before asking for confirmation or when the user asks about the transfer details.",148            "parameters": {149                "type": "object",150                "properties": {},151                "required": []152            }153        }154    },155    {156        "type": "function",157        "function": {158            "name": "create_illusion_account",159            "description": "Use this tool when the user needs an illusion account created for testing purposes. Requires the user's name.",160            "parameters": {161                "type": "object",162                "properties": {163                    "user_name": {164                        "type": "string",165                        "description": "The name of the user for the illusion account."166                    }167                },168                "required": ["user_name"]169            }170        }171    }172]173 174 175async def run_tool(tool_name: str, args: dict, telegram_id: int):176    if tool_name == "search_bank_knowledge":177        return await search_bank_knowledge(args["query"])178    if tool_name == "check_account_balance":179        return json.dumps(get_account_balance(telegram_id), ensure_ascii=False)180    if tool_name == "prepare_money_transfer":181        return json.dumps(182            prepare_transfer(183                telegram_id=telegram_id,184                receiver_serial_id=args["receiver_serial_id"],185                amount=args.get("amount"),186            ),187            ensure_ascii=False,188        )189    if tool_name == "confirm_money_transfer":190        return json.dumps(confirm_transfer(telegram_id), ensure_ascii=False)191    if tool_name == "cancel_money_transfer":192        return json.dumps(cancel_transfer(telegram_id), ensure_ascii=False)193    if tool_name == "get_pending_money_transfer":194        return json.dumps(get_pending_transfer(telegram_id), ensure_ascii=False)195    if tool_name == "create_illusion_account":196        return json.dumps(get_sender_account(telegram_id, args["user_name"]), ensure_ascii=False)197    return json.dumps({"success": False, "message": f"Unknown tool: {tool_name}"}, ensure_ascii=False)198 199async def get_ai_response(user_query: str, telegram_id: int):200    conversation_history = []201    if db_manager:202        raw_history = await db_manager.get_conversation_history(telegram_id, limit=6)203        raw_history.reverse()204        for msg in raw_history:205            if msg.get('message_text'):206                role = "user" if msg['message_type'] == 'user' else "assistant"207                conversation_history.append({"role": role, "content": msg['message_text']})208 209    transfer_instructions = TRANSFER_PROMPT.format(telegram_id=telegram_id)210    messages = [{"role": "system", "content": f"{BASE_PROMPT}\n\n{transfer_instructions}"}] + conversation_history + [{"role": "user", "content": user_query}]211    212    213    import asyncio214    loop = asyncio.get_event_loop()215    216    217    def call_hf(msgs):218        return hf_client.chat.completions.create(219            model=MODEL_NAME,220            messages=msgs,221            tools=TOOLS,222            tool_choice="auto",223            temperature=0.1,224            max_tokens=800225        )226 227    completion = await loop.run_in_executor(None, lambda: call_hf(messages))228    response_message = completion.choices[0].message229 230    for _ in range(4):231        if not response_message.tool_calls:232            break233 234        messages.append(response_message)235        for tool_call in response_message.tool_calls:236            args = json.loads(tool_call.function.arguments or "{}")237            tool_result = await run_tool(tool_call.function.name, args, telegram_id)238            messages.append({239                "role": "tool",240                "tool_call_id": tool_call.id,241                "content": tool_result242            })243 244        completion = await loop.run_in_executor(None, lambda: call_hf(messages))245        response_message = completion.choices[0].message246 247    return clean_ai_response(response_message.content if response_message.content else "")248