chat_with_agent_transfer.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. #!/usr/bin/env python3
  2. """
  3. 交互式Agent对话脚本
  4. 基于agno框架创建的终端对话界面
  5. """
  6. from agno.agent import Agent, RunResponse, RunResponseEvent
  7. from agno.models.openai import OpenAILike
  8. from agno.utils.pprint import pprint_run_response
  9. import os
  10. import sys
  11. import json
  12. import re
  13. from typing import Iterator
  14. import dotenv
  15. # 加载环境变量
  16. dotenv.load_dotenv()
  17. # 全局联系人数组
  18. CONTACTS = [
  19. {"name": "张三", "phone": "138-0000-1001"},
  20. {"name": "李四", "phone": "139-0000-1002"},
  21. {"name": "王五", "phone": "136-0000-1003"},
  22. {"name": "赵六", "phone": "137-0000-1004"},
  23. {"name": "孙七", "phone": "135-0000-1005"},
  24. {"name": "周八", "phone": "133-0000-1006"},
  25. {"name": "吴九", "phone": "188-0000-1007"},
  26. {"name": "郑十", "phone": "180-0000-1008"}
  27. ]
  28. from typing import Optional
  29. def find_contact_by_name(name: str) -> Optional[str]:
  30. """根据姓名查找联系人电话号码"""
  31. for contact in CONTACTS:
  32. if contact["name"] == name:
  33. return contact["phone"]
  34. return None
  35. def create_agent():
  36. """创建并配置Agent实例"""
  37. model = OpenAILike(
  38. id="qwen3-32b",
  39. api_key=os.getenv("BAILIAN_API_KEY"),
  40. base_url=os.getenv("BAILIAN_API_BASE_URL"),
  41. request_params={"extra_body": {"enable_thinking": False}},
  42. )
  43. def get_news(description: str):
  44. """示例工具函数 - 获取新闻"""
  45. print(f"[工具调用] get_news: {description}")
  46. return "暂无相关新闻"
  47. def extract_transfer_info(user_message: str) -> str:
  48. """从用户消息中提取转账信息
  49. Args:
  50. user_message: 用户的自然语言输入
  51. Returns:
  52. JSON字符串,包含提取的信息:
  53. {
  54. "is_transfer": true/false,
  55. "recipient_name": "姓名" (如果找到),
  56. "amount": "金额" (如果找到),
  57. "confidence": 0.0-1.0 (置信度)
  58. }
  59. """
  60. return f"请分析以下用户消息,判断是否包含转账意图,并提取相关信息:'{user_message}'"
  61. agent = Agent(
  62. model=model,
  63. tool_choice="auto",
  64. tools=[get_news, extract_transfer_info],
  65. add_datetime_to_instructions=True,
  66. show_tool_calls=True,
  67. markdown=True,
  68. instructions="""
  69. 你是一个智能助手,能够理解用户的各种需求。当用户表达转账意图时,你需要:
  70. 1. 识别转账关键词(如:转账、转钱、给...钱、汇款等)
  71. 2. 提取收款人姓名
  72. 3. 提取转账金额(如果有的话)
  73. 4. 返回JSON格式的结果
  74. 对于转账相关的消息,请使用extract_transfer_info工具来分析。
  75. JSON格式示例:
  76. {
  77. "is_transfer": true,
  78. "recipient_name": "张三",
  79. "amount": "100",
  80. "confidence": 0.9
  81. }
  82. 如果不是转账相关的消息,正常回答用户问题。
  83. """,
  84. )
  85. return agent
  86. def check_transfer_intent(user_input: str, agent: Agent) -> tuple[bool, Optional[str], Optional[str]]:
  87. """使用Agent检查用户输入是否包含转账意图
  88. Returns:
  89. tuple: (是否转账, 收款人姓名, 金额)
  90. """
  91. try:
  92. # 让Agent分析用户输入
  93. response = agent.run(f"请分析这句话是否包含转账意图,并提取相关信息:'{user_input}'")
  94. # 从Agent响应中提取信息
  95. response_text = ""
  96. if hasattr(response, 'content') and response.content:
  97. response_text = str(response.content)
  98. elif hasattr(response, 'messages') and response.messages:
  99. for msg in response.messages:
  100. if hasattr(msg, 'content') and msg.content:
  101. response_text += str(msg.content)
  102. # 尝试从响应中解析JSON
  103. import json
  104. # 查找JSON格式的数据
  105. json_match = re.search(r'\{[^}]*"is_transfer"[^}]*\}', response_text)
  106. if json_match:
  107. transfer_info = json.loads(json_match.group())
  108. is_transfer = transfer_info.get("is_transfer", False)
  109. recipient_name = transfer_info.get("recipient_name")
  110. amount = transfer_info.get("amount")
  111. confidence = transfer_info.get("confidence", 0.0)
  112. print(f"🔍 AI分析结果: 转账意图={is_transfer}, 置信度={confidence}")
  113. if recipient_name:
  114. print(f"📝 提取信息: 收款人={recipient_name}, 金额={amount or '未指定'}")
  115. # 只有在高置信度且确实是转账意图时才返回True
  116. if is_transfer and confidence > 0.7:
  117. return True, recipient_name, amount
  118. return False, None, None
  119. except Exception as e:
  120. print(f"⚠️ AI分析过程中出错: {e}")
  121. # 如果AI分析失败,回退到关键词检测
  122. transfer_keywords = ["转账", "转钱", "汇款", "给钱", "付款", "支付"]
  123. if any(keyword in user_input for keyword in transfer_keywords):
  124. print("🔍 使用关键词检测到转账意图")
  125. return True, None, None
  126. return False, None, None
  127. def get_missing_info_via_agent(agent: Agent, prompt: str) -> Optional[str]:
  128. """使用Agent获取缺失的信息
  129. Args:
  130. agent: Agent实例
  131. prompt: 询问提示
  132. Returns:
  133. 用户提供的信息,如果无法获取则返回None
  134. """
  135. try:
  136. print(f"\n🤖 Agent: {prompt}")
  137. user_response = input("🙋 您: ").strip()
  138. if not user_response:
  139. return None
  140. # 使用Agent分析用户响应
  141. analysis_prompt = f"用户说:'{user_response}',请提取出用户想要表达的具体信息(如姓名、金额、电话号码等),只返回提取到的关键信息,不要额外解释。"
  142. response = agent.run(analysis_prompt)
  143. # 从Agent响应中提取信息
  144. response_text = ""
  145. if hasattr(response, 'content') and response.content:
  146. response_text = str(response.content)
  147. elif hasattr(response, 'messages') and response.messages:
  148. for msg in response.messages:
  149. if hasattr(msg, 'content') and msg.content:
  150. response_text += str(msg.content)
  151. # 简单清理响应文本,提取关键信息
  152. cleaned_response = response_text.strip()
  153. # 移除常见的解释性词语
  154. for phrase in ["用户想要表达的是", "提取到的信息是", "关键信息是", "具体信息是"]:
  155. cleaned_response = cleaned_response.replace(phrase, "").strip()
  156. return cleaned_response if cleaned_response else user_response
  157. except Exception as e:
  158. print(f"⚠️ AI分析用户输入时出错: {e}")
  159. return user_response if 'user_response' in locals() else None
  160. def handle_transfer_with_extracted_info(agent: Agent, name: Optional[str] = None, amount: Optional[str] = None):
  161. """处理转账功能(使用已提取的信息和Agent补全缺失信息)"""
  162. # 如果没有提取到姓名,使用Agent询问
  163. if not name:
  164. print("📝 需要获取收款人信息...")
  165. name = get_missing_info_via_agent(agent, "请告诉我您要转账给谁?(请提供收款人的姓名)")
  166. if not name:
  167. print("⚠️ 无法获取收款人姓名,转账操作取消")
  168. return
  169. print(f"\n💰 准备转账给: {name}")
  170. # 查找联系人电话号码
  171. phone = find_contact_by_name(name)
  172. if phone:
  173. print(f"📞 找到联系人电话: {phone}")
  174. else:
  175. print(f"❌ 联系人列表中未找到 {name} 的电话号码")
  176. phone = get_missing_info_via_agent(agent, f"请提供 {name} 的电话号码:")
  177. if not phone:
  178. print("⚠️ 无法获取电话号码,转账操作取消")
  179. return
  180. # 验证电话号码格式
  181. # 先去除所有非数字字符,只保留数字
  182. def normalize_phone(phone_str):
  183. return re.sub(r'\D', '', phone_str or "")
  184. while phone:
  185. normalized_phone = normalize_phone(phone)
  186. if re.match(r"^1\d{10}$", normalized_phone):
  187. phone = normalized_phone # 用标准化后的号码
  188. break
  189. print("⚠️ 电话号码格式不正确,您之前输入的电话号码是:", phone)
  190. phone = get_missing_info_via_agent(agent, "请提供正确格式的电话号码(如:138-0000-1001 或 13800001001):")
  191. if not phone:
  192. print("⚠️ 无法获取有效电话号码,转账操作取消")
  193. return
  194. # 处理转账金额
  195. validated_amount = None
  196. if amount:
  197. # 验证AI提取的金额
  198. try:
  199. amount_float = float(amount)
  200. if amount_float > 0:
  201. validated_amount = amount
  202. print(f"💵 转账金额: ¥{amount}")
  203. else:
  204. print(f"⚠️ 提取的金额 {amount} 无效")
  205. amount = None # 重置amount,触发重新获取
  206. except ValueError:
  207. print(f"⚠️ 提取的金额 {amount} 格式错误")
  208. amount = None # 重置amount,触发重新获取
  209. # 如果没有有效金额,使用Agent询问
  210. if not validated_amount:
  211. print("💵 需要获取转账金额...")
  212. while True:
  213. amount_str = get_missing_info_via_agent(agent, f"请告诉我您要转账多少钱给 {name}?(请输入数字金额)")
  214. if not amount_str:
  215. print("⚠️ 无法获取转账金额,转账操作取消")
  216. return
  217. try:
  218. # 从用户输入中提取数字
  219. number_match = re.search(r'\d+(?:\.\d+)?', amount_str)
  220. if number_match:
  221. amount_float = float(number_match.group())
  222. if amount_float > 0:
  223. validated_amount = str(amount_float)
  224. print(f"✅ 确认转账金额: ¥{validated_amount}")
  225. break
  226. else:
  227. print("⚠️ 转账金额必须大于0")
  228. else:
  229. print("⚠️ 无法从输入中识别有效的金额数字")
  230. except ValueError:
  231. print("⚠️ 金额格式错误,请重新输入")
  232. # 创建转账信息JSON
  233. transfer_info = {
  234. "recipient_name": name,
  235. "recipient_phone": phone,
  236. "amount": validated_amount,
  237. "timestamp": __import__('datetime').datetime.now().isoformat()
  238. }
  239. # 显示转账信息
  240. print("\n" + "="*50)
  241. print("📋 转账信息确认:")
  242. print(f" 收款人: {transfer_info['recipient_name']}")
  243. print(f" 电话号码: {transfer_info['recipient_phone']}")
  244. print(f" 转账金额: ¥{transfer_info['amount']}")
  245. print(f" 时间: {transfer_info['timestamp']}")
  246. print("="*50)
  247. # 确认转账
  248. confirm_response = input("请确认以上转账信息是否正确?(回答:y/n)").strip()
  249. if confirm_response:
  250. confirm_response_str_raw = str(confirm_response).strip().lower()
  251. if confirm_response_str_raw == "y":
  252. # 用户确认,保存转账信息
  253. try:
  254. transfers_file = "transfer_records.json"
  255. if os.path.exists(transfers_file):
  256. with open(transfers_file, 'r', encoding='utf-8') as f:
  257. transfers = json.load(f)
  258. else:
  259. transfers = []
  260. # 添加新转账记录,并附加用户确认回复
  261. transfer_info_with_confirm = dict(transfer_info)
  262. transfer_info_with_confirm["user_confirm_response"] = '{"is_transfer": true}'
  263. transfers.append(transfer_info_with_confirm)
  264. with open(transfers_file, 'w', encoding='utf-8') as f:
  265. json.dump(transfers, f, ensure_ascii=False, indent=2)
  266. print("✅ 转账信息已保存!")
  267. print(f"📄 转账记录已保存到: {transfers_file}")
  268. except Exception as e:
  269. print(f"❌ 保存转账记录时出错: {e}")
  270. elif confirm_response_str_raw == "n":
  271. print("❌ 用户未确认转账,未保存转账信息。")
  272. else:
  273. print("⚠️ 无法识别的回复,转账操作取消")
  274. else:
  275. print("⚠️ 未收到确认回复,转账操作取消")
  276. def print_welcome():
  277. """打印欢迎信息"""
  278. print("=" * 60)
  279. print("🤖 欢迎使用Agent对话系统!")
  280. print("=" * 60)
  281. print("💡 使用说明:")
  282. print(" - 直接输入您的问题进行对话")
  283. print(" - 输入 'exit', 'quit', 'bye' 或 '退出' 结束对话")
  284. print(" - 输入 'clear' 或 '清屏' 清空屏幕")
  285. print(" - 输入 'help' 或 '帮助' 查看帮助信息")
  286. print(" - 说出转账相关的话(如:给张三转100块钱)进行转账操作")
  287. print("=" * 60)
  288. def print_help():
  289. """打印帮助信息"""
  290. print("\n📋 帮助信息:")
  291. print(" exit/quit/bye/退出 - 退出程序")
  292. print(" clear/清屏 - 清空屏幕")
  293. print(" help/帮助 - 显示此帮助信息")
  294. print(" 转账相关自然语言 - AI智能识别转账意图")
  295. print(" 例:给张三转100块 - 转账功能示例")
  296. print(" 其他任何内容 - 与Agent对话")
  297. def clear_screen():
  298. """清空屏幕"""
  299. os.system('cls' if os.name == 'nt' else 'clear')
  300. def chat_loop():
  301. """主对话循环"""
  302. agent = create_agent()
  303. print_welcome()
  304. while True:
  305. try:
  306. # 获取用户输入
  307. user_input = input("\n🙋 您: ").strip()
  308. # 检查退出命令
  309. if user_input.lower() in ['exit', 'quit', 'bye', '退出']:
  310. print("\n👋 再见! 感谢使用Agent对话系统!")
  311. break
  312. # 检查清屏命令
  313. if user_input.lower() in ['clear', '清屏']:
  314. clear_screen()
  315. print_welcome()
  316. continue
  317. # 检查帮助命令
  318. if user_input.lower() in ['help', '帮助']:
  319. print_help()
  320. continue
  321. # 检查是否包含转账意图(让AI判断)
  322. is_transfer, name, amount = check_transfer_intent(user_input, agent)
  323. if is_transfer:
  324. handle_transfer_with_extracted_info(agent, name, amount)
  325. continue
  326. # 检查空输入
  327. if not user_input:
  328. print("⚠️ 请输入您的问题或命令")
  329. continue
  330. print(f"\n🤖 Agent: ")
  331. print("-" * 50)
  332. # 运行agent并获取响应流
  333. response_stream: Iterator[RunResponseEvent] = agent.run(
  334. user_input,
  335. stream=True
  336. )
  337. # 打印响应
  338. pprint_run_response(response_stream, markdown=True)
  339. except KeyboardInterrupt:
  340. print("\n\n👋 检测到 Ctrl+C,正在退出...")
  341. break
  342. except Exception as e:
  343. print(f"\n❌ 发生错误: {e}")
  344. print("请重试或联系技术支持")
  345. def main():
  346. """主函数"""
  347. try:
  348. # 检查环境变量
  349. if not os.getenv("BAILIAN_API_KEY") or not os.getenv("BAILIAN_API_BASE_URL"):
  350. print("❌ 错误: 请确保设置了以下环境变量:")
  351. print(" - BAILIAN_API_KEY")
  352. print(" - BAILIAN_API_BASE_URL")
  353. print("\n💡 您可以创建 .env 文件来设置这些变量")
  354. sys.exit(1)
  355. # 开始对话
  356. chat_loop()
  357. except Exception as e:
  358. print(f"❌ 启动失败: {e}")
  359. sys.exit(1)
  360. if __name__ == "__main__":
  361. main()