[AI Agent 01]对话记忆、Agent 循环、Function Calling
代码是AI写的用的CC接入DS-v4-pro代码功能包含对话记忆、Agent 循环、Function Calling本篇文章仅作为个人学习笔记记录分享完整代码如下 Agent 初体验 —— 让 AI 能运行 Python 代码 核心概念 Function Calling 1. 告诉 AI 你有一个工具可以用 2. AI 判断需要用工具时会请求调用它 3. 我们帮 AI 执行工具把结果返回 4. AI 看到结果后组织语言回答用户 这整个循环就叫 Agent Loop智能体循环 import sys import os sys.stdout.reconfigure(encodingutf-8) from openai import OpenAI from dotenv import load_dotenv import json import io load_dotenv() client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com, ) # 第1步定义一个工具函数 # 这个函数真的会执行 Python 代码并把结果返回 def run_python_code(code: str) - str: 执行 Python 代码并返回结果 try: # 用 StringIO 拦截 print 的输出 old_stdout sys.stdout captured io.StringIO() sys.stdout captured local_vars {} exec(code, {}, local_vars) sys.stdout old_stdout # 恢复标准输出 output captured.getvalue().strip() if output: return output if local_vars: return str(list(local_vars.values())[-1]) return 代码执行完成无输出 except Exception as e: sys.stdout old_stdout # 出错也要恢复 return f执行出错{e} # 第2步把工具注册给 DeepSeek # 用 JSON 格式描述这个工具是干嘛的、接受什么参数 tools [ { type: function, function: { name: run_python_code, description: 执行一段 Python 代码并返回结果。当你需要计算、处理数据时使用此工具。, parameters: { type: object, properties: { code: { type: string, description: 要执行的 Python 代码比如 sum(range(1, 101)), } }, required: [code], }, }, } ] # 工具名 → 真实函数的映射表 tool_map {run_python_code: run_python_code} # 第3步Agent 主循环 messages [ {role: system, content: 你是一个能执行Python代码的助手。遇到需要计算的问题时调用 run_python_code 工具。最后用中文回答用户。}, ] print( * 50) print(AI Agent 聊天室 —— 我能帮你算数学、处理数据) print(试试说1加到100是多少 或 帮我算 3的100次方) print(输入 quit 退出) print( * 50) while True: user_input input(\n你: ) if user_input.lower() quit: print(再见) break messages.append({role: user, content: user_input}) # 第1次调用AI 可能选择直接回答也可能选择调用工具 response client.chat.completions.create( modeldeepseek-chat, messagesmessages, toolstools, ) reply response.choices[0].message # ---------- 关键检查 AI 是否想调用工具 ---------- if reply.tool_calls: # AI 说我想用工具 for tool_call in reply.tool_calls: func_name tool_call.function.name func_args json.loads(tool_call.function.arguments) print(f [Agent 调用工具: {func_name}({func_args})]) # 真正执行这个工具函数 tool_result tool_map[func_name](**func_args) print(f [工具返回: {tool_result[:60]}...]) # 把工具调用和结果都加入记忆 messages.append( { role: assistant, content: reply.content or , tool_calls: [ { id: tool_call.id, type: function, function: { name: func_name, arguments: tool_call.function.arguments, }, } ], } ) messages.append( { role: tool, tool_call_id: tool_call.id, content: tool_result, } ) # 第2次调用把工具结果发给 AI让它总结 response client.chat.completions.create( modeldeepseek-chat, messagesmessages, ) reply response.choices[0].message # ---------- 打印最终回复 ---------- print(fAI: {reply.content}) # 把 AI 的最终回复记入历史 messages.append({role: assistant, content: reply.content})关于我不会的东西1.与内置标准库模块sys和os对接后续可以借用其中功能import sys import os2.reconfigure用于运行时更改参数(形参实参)函数定义时可以有形参可以无形参def 函数 (形参实参,形参实参):若定义时def 函数 (形参形参):则调用时可以函数(实参,实参)实参与形参会自动对应sys.stdout.reconfigure(encodingutf-8)3.从【包名】中调用【类名/函数名】from openai import OpenAI from dotenv import load_dotenv4.OpenAI(...)调用类来实例化括号里可以传递参数这些参数会传给类的 构造方法 __init__用来初始化新创建的对象。构造方法def __init__(self,name,age):同一个类可以创建出无数个独立的实例每个实例可以有自己的数据os.getenv()读取环境变量client OpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlhttps://api.deepseek.com, )5.列表[ ]记录对话的顺序其中的元素为字典{ }由键值对构成形式相当于“标签:内容”role system系统/用户user/助手assistant可帮助辨认身份content 具体说的话可记忆对应发言messages[ {role: system, content: 你是一个友好的助手喜欢用简洁的方式回答问题。}, {role: user, content: 你好请用一句话介绍你自己。}, ]6.[0]的意思是把列表里的第1个选项拿出来response.choices[0].message.content7..lower()方法将字符串中的所有字母都变成小写if user_input.lower() quit:8.client.chat.completions.create()链式调用messagesmessages参数名值作用是把整个对话历史列表传给 AI等号左边的messages是函数内部定义好的参数名等号右边的messages是在程序里定义的变量如果不想混淆也可以写成messagesconversation_historyresponse client.chat.completions.create( modeldeepseek-chat, messagesmessages, )9.def定义函数run_python_code表示函数名code: str表示参数名为 code 同时类型是字符串- str表示返回值类型是字符串def run_python_code(code: str) - str:10.old_stdout保存输出到屏幕captured创建一个内存中的字符串缓冲区sys.stdout captured把输出重定向到缓冲区sys.stdout代表“标准输出流”默认就是终端io.StringIO()是一个伪装成文件的内存字符串创建一个内存中的文件对象行为类似于一个文本文件但数据存储在内存字符串中有.write()方法写入字符串以及.getvalue()方法获取里面全部内容old_stdout sys.stdout captured io.StringIO() sys.stdout captured11.try-except尝试执行代码如果出错就捕获异常try: except Exception as e: sys.stdout old_stdout return f执行出错{e}12.exec(code, globals, locals)exec()是 Python 的内置函数它接收一个字符串并把这个字符串当作 Python 代码来执行globals参数 {}是空字典。表示执行这段代码时全局作用域是空的这意味着代码中不能访问外部的任何全局变量也不能修改外部的全局变量local_vars是一个空字典但在 exec 执行过程中代码中定义的所有局部变量都会自动存储到这个字典中local_vars {} exec(code, {}, local_vars)注意区别captured.getvalue()获取print的输出local_vars字典获取代码执行后产生的所有局部变量13.parameters: {...}本质是字典但是是JSON Schema格式是固定的键是固定的type: object意味着这个参数的类型是object对象而在 JSON 里对象就是花括号括起来的一组键值对parameters: { type: object, properties: { code: { type: string, description: 要执行的 Python 代码比如 sum(range(1, 101)), } }, required: [code], }14.创建了从函数名到实际函数的映射关系在键值对中键run_python_code是字符串仅代表名称值run_python_code没有双引号同时没有括号代表并不在调用函数仅代表函数本身tool_map {run_python_code: run_python_code}15.tool_call.function.name是字符串对应之前在tools 列表中定义的工具名tool_call.function.arguments是字符串内容是 JSON 格式的参数{键: 值}json.loads(...)把AI返回的argumentsJSON 字符串解析成 Python 字典即变成{键: 值}然后可以直接用字典键来访问参数值for tool_call in reply.tool_calls: func_name tool_call.function.name func_args json.loads(tool_call.function.arguments)16.tool_map[func_name取得函数对象(**func_args是字典解包语法把func_args字典中的键值对传递给函数eg如果func_args {code:print(12)}则调用等价于run_python_code(codeprint(12))tool_result就是函数执行后的返回值tool_result tool_map[func_name](**func_args)