{ "cells": [ { "cell_type": "markdown", "id": "4a50f764", "metadata": {}, "source": [ "### 练习3. 转账Agent\n", "设计一个 Agent, 能够协助用户完成转账操作\n", "\n", "#### 目标\n", "使用 qwen3-32b 模型\n", "1. 向用户收集\n", " 1. 对方姓名(写死只有“张三”存在并返回手机号,其他都不存在)\n", " 2. 对方卡号\n", " 3. 转账金额(只有500)\n", "2. 收集完成后,调用转账接口向对方转账并提示用户转账结果\n", "3. 如果转账失败用户可以重新修改信息并再次发起转账\n", "\n", "#### 要求\n", "1. 给 Agent 提供必要的工具,Agent 来决定调用哪个工具。\n", "2. 但是 agent必须要能够正确的用户给的参数来调用对应的方法\n", "\n", "#### 场景\n", "1. Agent在接收满足上面 tools要求的情况下, 校验成功转账\n", "2. Agent 在接收不满足上面 tools要求的情况下, 和用户澄清并让用户修改信息。用户修改正确后,可以完成转账。\n", " 1. 余额不足,和用户沟通,用户只能转少于500块\n", " 2. 其他:账号手机号不存在,用户可以手动输入手机号\n" ] }, { "cell_type": "code", "execution_count": null, "id": "d6203ac3", "metadata": {}, "outputs": [], "source": [ "import os\n", "from dotenv import load_dotenv\n", "\n", "load_dotenv()\n", "base_url = os.getenv('BAILIAN_API_BASE_URL')\n", "api_key = os.getenv('BAILIAN_API_KEY')" ] }, { "cell_type": "code", "execution_count": 9, "id": "41fd0038", "metadata": {}, "outputs": [], "source": [ "from agno.agent import Agent\n", "from agno.tools import tool\n", "from agno.memory.v2.db.sqlite import SqliteMemoryDb\n", "from agno.memory.v2.memory import Memory\n", "from agno.storage.sqlite import SqliteStorage\n", "from agno.models.openai.like import OpenAILike\n", "from typing import Optional, Tuple\n", "from dataclasses import dataclass\n", "from enum import Enum\n", "import time\n", "\n", "SYSTEM_PROMPT = \"\"\"\n", "你是一个转账助手,帮助用户完成转账操作。\n", "\n", "规则:\n", "1. 系统中只存在\"张三\"这个用户\n", "2. 最大转账金额为不能超过用户的余额\n", "3. 需要验证收款人姓名、账号和金额\n", "4. 如果转账失败,根据具体错误原因协助用户修改信息:\n", " - 如果用户不存在,提醒用户检查收款人姓名\n", " - 如果手机号不匹配,提醒用户确认手机号\n", " - 如果余额不足,建议用户调整转账金额\n", "5. 如果用户有其他疑问,提供相关帮助\n", "\n", "请按照以下步骤操作:\n", "1. 开场先介绍自己是转账助手,询问用户接下来的操作,如果是转账,则按照下面的步骤;如果是其他问题,则提供相关帮助。\n", "2. 收集用户信息:收款人姓名、账号、转账金额\n", "3. 验证信息的正确性,如果信息不完整或错误,引导用户修改\n", "4. 执行转账操作,如果转账成功,告知用户转账成功\n", "5. 如果失败,引导用户修改信息重试\n", "\"\"\"\n", "\n", "# Mock database for user info\n", "USER_DB = {\n", " \"张三\": \"13800138000\"\n", "}\n", "\n", "# UserId for the memories\n", "user_id = \"test\"\n", "# Database file for memory and storage\n", "db_file = \"tmp/agent.db\"\n", "\n", "model = OpenAILike(\n", " id=\"qwen3-32b\",\n", " api_key=api_key,\n", " base_url=base_url,\n", " request_params={\"extra_body\": {\"enable_thinking\": False}},\n", " )\n", "\n", "# Initialize memory.v2\n", "memory = Memory(\n", " # Use any model for creating memories\n", " model=model,\n", " db=SqliteMemoryDb(table_name=\"user_memories\", db_file=db_file),\n", ")\n", "# Initialize storage\n", "storage = SqliteStorage(table_name=\"agent_sessions\", db_file=db_file)\n", "\n", "# Tools for the agent\n", "def get_contact(user_name: str) -> Optional[str]:\n", " return USER_DB.get(user_name, None)\n", "\n", "def get_balance() -> float:\n", " return 500.0\n", "\n", "def reply_to_user(message: str) -> None:\n", " print(message)\n", "\n", "def validate_amount(amount: float) -> bool:\n", " return amount <= get_balance()\n", "\n", "class TransferError(Enum):\n", " USER_NOT_FOUND = \"用户不存在\"\n", " INVALID_PHONE = \"手机号不匹配\"\n", " INSUFFICIENT_BALANCE = \"余额不足\"\n", " NONE = \"无错误\"\n", "\n", "@dataclass\n", "class TransferResult:\n", " success: bool\n", " error: TransferError\n", " message: str\n", "\n", "def validate_transfer(user_name: str, phone: str, amount: float) -> TransferResult:\n", " \"\"\"验证转账信息并返回详细结果\"\"\"\n", " if user_name not in USER_DB:\n", " return TransferResult(False, TransferError.USER_NOT_FOUND, f\"收款人 {user_name} 不存在\")\n", " \n", " if not validate_amount(amount):\n", " return TransferResult(False, TransferError.INSUFFICIENT_BALANCE, \n", " f\"转账金额 {amount} 超过账户余额 {get_balance()}\")\n", " \n", " if phone != get_contact(user_name):\n", " return TransferResult(False, TransferError.INVALID_PHONE, \n", " f\"手机号 {phone} 与用户信息不匹配\")\n", " \n", " return TransferResult(True, TransferError.NONE, \"验证通过\")\n", "\n", "def transfer(user_name: str, phone: str, amount: float) -> Tuple[bool, str]:\n", " \"\"\"执行转账操作,返回转账结果和详细信息\"\"\"\n", " result = validate_transfer(user_name, phone, amount)\n", " if result.success:\n", " return True, \"转账成功\"\n", " return False, result.message\n", "\n", "agent = Agent(\n", " model=model,\n", " tools=[validate_amount, transfer, reply_to_user, get_contact, get_balance],\n", " instructions=SYSTEM_PROMPT,\n", " add_datetime_to_instructions=True,\n", " show_tool_calls=True,\n", " markdown=True,\n", " memory=memory,\n", " enable_user_memories=True,\n", " storage=storage,\n", " add_history_to_messages=True,\n", ")" ] }, { "cell_type": "code", "execution_count": 10, "id": "fb2117b1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "转账助手已启动。请开始对话。\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "a95695bf39f84f5b8c9cdb1251f7995f", "version_major": 2, "version_minor": 0 }, "text/plain": [ "Output()" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fdfb2b68c45b44619135d51ba709421a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "4e508c5b43a846748cb26de3897265e3",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "fbff122df31749959d4b2af37d9e579e",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5237ded427d64f20a9157511cafe0c6a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "daaa1ff32aeb4f98b0c738dc555cf327",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "608035432a5d44039c32628a1fc8d460",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "a548a49f4dc0491abb69679c71b4d46a",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e745199fedab4c898e105ee1b3311a25",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Output()"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "
\n"
      ],
      "text/plain": []
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "对话结束。\n"
     ]
    }
   ],
   "source": [
    "print(\"转账助手已启动。请开始对话。\")\n",
    "agent.print_response(\"你好\", stream=True, user_id=user_id)\n",
    "while True:\n",
    "    user_input = input(\"用户: \")\n",
    "    if user_input.lower() in ['exit', 'quit']:\n",
    "        print(\"对话结束。\")\n",
    "        break\n",
    "    if not user_input.strip():\n",
    "        print(\"输入不能为空,请重新输入。\")\n",
    "        continue\n",
    "    # print(f\"用户: {user_input}\")\n",
    "\n",
    "    agent.print_response(user_input, stream=True, user_id=user_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "95207ed5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Memories about 上个会话:\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "
[\n",
       "UserMemory(\n",
       "│   │   memory='Her phone number is 13800138000.',\n",
       "│   │   topics=['contact'],\n",
       "│   │   input='手机号:13800138000',\n",
       "│   │   last_updated=datetime.datetime(2025, 7, 22, 15, 55, 33, 469818),\n",
       "│   │   memory_id='e9590a07-edbe-410b-b336-c454654dbd8f'\n",
       ")\n",
       "]\n",
       "
\n" ], "text/plain": [ "\u001b[1m[\u001b[0m\n", "\u001b[2;32m│ \u001b[0m\u001b[1;35mUserMemory\u001b[0m\u001b[1m(\u001b[0m\n", "\u001b[2;32m│ │ \u001b[0m\u001b[33mmemory\u001b[0m=\u001b[32m'Her phone number is 13800138000.'\u001b[0m,\n", "\u001b[2;32m│ │ \u001b[0m\u001b[33mtopics\u001b[0m=\u001b[1m[\u001b[0m\u001b[32m'contact'\u001b[0m\u001b[1m]\u001b[0m,\n", "\u001b[2;32m│ │ \u001b[0m\u001b[33minput\u001b[0m=\u001b[32m'手机号:13800138000'\u001b[0m,\n", "\u001b[2;32m│ │ \u001b[0m\u001b[33mlast_updated\u001b[0m=\u001b[1;35mdatetime\u001b[0m\u001b[1;35m.datetime\u001b[0m\u001b[1m(\u001b[0m\u001b[1;36m2025\u001b[0m, \u001b[1;36m7\u001b[0m, \u001b[1;36m22\u001b[0m, \u001b[1;36m15\u001b[0m, \u001b[1;36m55\u001b[0m, \u001b[1;36m33\u001b[0m, \u001b[1;36m469818\u001b[0m\u001b[1m)\u001b[0m,\n", "\u001b[2;32m│ │ \u001b[0m\u001b[33mmemory_id\u001b[0m=\u001b[32m'e9590a07-edbe-410b-b336-c454654dbd8f'\u001b[0m\n", "\u001b[2;32m│ \u001b[0m\u001b[1m)\u001b[0m\n", "\u001b[1m]\u001b[0m\n" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from rich.pretty import pprint\n", "\n", "print(\"Memories about 上个会话:\")\n", "pprint(memory.get_user_memories(user_id=user_id))" ] } ], "metadata": { "kernelspec": { "display_name": "ai-learning", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.13" } }, "nbformat": 4, "nbformat_minor": 5 }