follow up datawhalechina/Agent-Learning-Hub: AI Agent 学习路线与资料库收集
Stage 1: Build A Minimal Agent Loop
一个简单的agent对话机器人
和agent建立连接
没用langchain因为电脑内存不够了
1 2
| import dotenv from dotenv import load_dotenv
|
然后在.env里面存一下apikey和base_url,方便管理同时不在功能代码里出现api扣钱
1
| miniagent = OpenAI(api_key = os.getenv("OPENAI_API_KEY"),base_url = os.getenv("OPENAI_BASE_URL"),)
|
注册一下agent信息,然后写调用函数
1 2 3 4 5 6 7
| def call_llm(message : List[Dict[str,Any]]) -> str: response = miniagent.chat.completions.create( model="deepseek-v4-flash", messages=messages, temperature=0.7, ) return response.choices[0].message.content
|
定义并且注册工具
定义工具和函数一样,正常执行功能就可以,唯一比较不同的是多定义一个字典,存储工具名称、工具的功能描述、以及工具的参数列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| TOOLS_REGISTER = { "calculator": calculator, "get_current_time": get_current_time, "get_weather": get_weather, }
TOOLS_DESCRIPTIONS = { "calculator": "计算表达式,支持 sin/cos/tan数学函数", "get_current_time": "获取当前时间", "get_weather": "获取指定城市的天气信息", } TOOLS_DETAILS = { "calculator": { "parameters": { "expression": {"type": "string", "description": "数学表达式"} }, "required": ["expression"] }, "get_current_time": { "parameters": {}, "required": [] }, "get_weather": { "parameters": { "city": {"type": "string", "description": "城市名"}, "days": {"type": "integer", "description": "天数(1-3)", "default": 1} }, "required": ["city"] } }
|
最开始的时候没有单独把工具功能描述和工具的属性列表拆开,导致agent效果很差,调用工具的时候传入的参数名字是错的导致工具调用失败,很多次都这样,然后查了一下claude-code是会在确定使用这个工具的时候才传入这个工具的参数列表,虽然多了一次llm调用,但是在具体表现上稳定了很多。
Agent循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| def agent_loop(user_input:str, max_step: int = 3, timeout:int = 30) -> str: start_time = time.time() step1_prompt = f'''你是个.....,你需要完成用户的任务,你可以选择使用工具辅助你完成,如果能够直接完成任务,可以直接回复。注册的工具有:{TOOLS_DESCRIPTION} - 如果用户问题可以直接回答,不需要工具,请直接回答。 - 如果需要工具,请只输出工具名称,格式:TOOL:工具名 - 每次只能调用一个工具.''' messages = [ { "role":"system", "content":step1_prompt }, { "role":"user", "content" : user_input } ] step = 0 while step < max_step : if(time.time()-start_time > timeout) return "超出应答时间" response = call_llm(messages) step += 1 if not response.startswith("TOOL:"): return response tool_name = response.replace("TOOL:","").strip() step2_prompt = f"""{step1_prompt} 用户问题:{user_input} 你决定使用工具:{tool_name} 工具的参数有:{TOOLS_DETAILS[tool_name]} 请输出严格的 JSON 格式: {{"tool": "{tool_name}", "arguments": {{"参数名": "参数值"}}}} """ tool_messages = [ { "role" :"system", "content" :step2_prompt }, { "role" : "user", "content" : user_input } ] tool_response = call_llm(tool_messages) step += 1 try: tool_call = json.loads(tool_response) tool_name = tool_call["tool"] arguments = tool_call["arguments"] result = execute_tool(tool_name, arguments) messages.append({"role":"assistant","content":f"使用了工具{tool_name}"}) messages.append({"role":"user","content":f"工具执行结果{result}"})
|
逻辑就是先给模型,看模型选不选择调用工具,然后再把模型选择的工具需要的参数给模型,让模型给出如何调用工具,返回结果后调用工具把函数返回值再给模型。
具体示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 助手启动,输入 'exit' 退出 用户: 你好,请你告诉我长沙的天气好吗 [Step 0] 助手输出: TOOL:get_weather [Step 1] 工具调用 JSON: {"tool": "get_weather", "arguments": {"city": "长沙"}} 从tool工具调用,获取未来1天的天气信息 [Step 2] 工具结果: { "temp": 26, "humidity": 62, "wind": 3, "cloud": 70, "rain": 0.0, "snow": 0.0, "sunrise": "06:00", "sunset": "18:00" } [Step 2] 助手输出: 长沙现在的天气很不错哦!🌤️
- **气温**:26°C,非常舒适,不冷不热。 - **湿度**:62%,比较适中,不会感觉闷。 - **风力**:3级,微风拂面。 - **云量**:70%,多云为主,但无降雨或降雪。 - **日出/日落**:06:00 / 18:00,白天时间充足。
总的来说,今天是个适合外出活动的好天气,不需要带伞,享受阳光和微风吧!😊 最终回答: 长沙现在的天气很不错哦!🌤️
- **气温**:26°C,非常舒适,不冷不热。 - **湿度**:62%,比较适中,不会感觉闷。 - **风力**:3级,微风拂面。 - **云量**:70%,多云为主,但无降雨或降雪。 - **日出/日落**:06:00 / 18:00,白天时间充足。
总的来说,今天是个适合外出活动的好天气,不需要带伞,享受阳光和微风吧!😊 用户: 闭麦是对小麦发言权的一种侵害这对吗 [Step 0] 助手输出: 这话是个谐音梗,带点幽默讽刺。“闭麦”通常指关闭麦克风,而“小麦” 是农作物,没有发言权一说。所以从字面逻辑上不对,从玩笑角度看,它是在调侃“闭麦”这个词的字面意思。答案是:不对,这是幽默的说法。 最终回答: 这话是个谐音梗,带点幽默讽刺。“闭麦”通常指关闭麦克风,而“小麦”是农作物,没有发言权一说。所以从字面逻辑上不对,从玩笑角度看,它是在调侃“闭麦”这个词的字面意思。答案是:不对,这是幽默的说法。 用户:
|
碎碎念
还不错完成了一整个流程,虽然很简单,原本上网课一直在概念阶段,动手觉得挺开心的?