智能体初体验:实现一个“旅游景点推荐助手”
2026/9/16 6:46:28 网站建设 项目流程

作业打卡|Datawhale《Hello-Agents》第一章实践

1. 作业背景

本章教程的目标是实现一个“旅游景点推荐助手”:用户输入一句话,智能体先查询天气,再根据天气推荐景点。它体现的是典型的Thought-Action-Observation循环:

用户请求 -> Thought: 先查天气 -> Action: get_weather(city="...") -> Observation: 天气结果 -> Thought: 根据天气选景点 -> Action: get_attraction(city="...", weather="...") -> Observation: 景点结果 -> Action: Finish[最终建议]

教程原版使用Tavily Search API做景点搜索。但我在注册 Tavily 账号时一直卡在验证环节,无法获取 API Key;同时考虑到国内网络访问部分搜索后端不稳定,所以我把“景点搜索工具”从 Tavily 改成了高德地图 POI 查询 Web 服务。改动后流程不变,仍然保留 ReAct 智能体的核心思想,只是把外部工具换成了国内更容易跑通的数据源。

2. 我的改动点

相比教程代码,我主要做了 4 处调整:

  1. 景点工具替换:删除tavily-python,改用高德https://restapi.amap.com/v3/place/text查询 POI。
  2. 密钥管理:所有 Key 都从环境变量读取,不写死在代码里,避免作业截图/文章泄露。
  3. 解析器加固:教程里Action: (.*)Finish\[(.*)\]在多行输出时容易匹配失败,我改成按行解析 Action,并允许Finish[...]内出现多行最终答案。
  4. 天气到景点关键词映射:根据天气文本选择高德 POI 关键词,例如天气差查“博物馆”,偏热查“水上乐园”,晴好查“公园、名胜古迹”。

3. 环境与依赖

pipinstallrequests openai

需要准备两个 Key,并设置到环境变量:

# ModelScope 或其他兼容 OpenAI 接口的大模型服务exportMODELSCOPE_API_KEY="你的大模型Key"# 高德开放平台 Web服务 Key,注意不是 JS API KeyexportAMAP_KEY="你的高德Web服务Key"

Windows PowerShell 可用:

setx MODELSCOPE_API_KEY"你的大模型Key"setx AMAP_KEY"你的高德Web服务Key"

4. 核心代码

4.1 系统提示词:规定 Thought/Action 格式

AGENT_SYSTEM_PROMPT=""" 你是一个智能旅行助手。你的任务是分析用户的请求,并使用可用工具一步步地解决问题。 # 可用工具: - `get_weather(city: str)`: 查询指定城市的实时天气。 - `get_attraction(city: str, weather: str)`: 根据城市和天气搜索推荐的旅游景点。 # 输出格式要求: 你的每次回复必须严格遵循以下格式,包含一对Thought和Action: Thought: [你的思考过程和下一步计划] Action: [你要执行的具体行动] Action的格式必须是以下之一: 1. 调用工具:function_name(arg_name="arg_value") 2. 结束任务:Finish[最终答案] # 重要提示: - 每次只输出一对Thought-Action - Action必须在同一行,不要换行 - 当收集到足够信息可以回答用户问题时,必须使用 Action: Finish[最终答案] 格式结束 请开始吧! """

4.2 工具一:查询天气

importrequestsdefget_weather(city:str)->str:"""通过 wttr.in 查询真实天气。"""url=f"https://wttr.in/{city}?format=j1"try:response=requests.get(url,timeout=10)response.raise_for_status()data=response.json()current_condition=data['current_condition'][0]weather_desc=current_condition['weatherDesc'][0]['value']temp_c=current_condition['temp_C']returnf"{city}当前天气:{weather_desc},气温{temp_c}摄氏度"exceptrequests.exceptions.RequestExceptionase:returnf"错误:查询天气时遇到网络问题 -{e}"except(KeyError,IndexError)ase:returnf"错误:解析天气数据失败,可能是城市名称无效 -{e}"

4.3 工具二:高德 POI 景点推荐

这是替换 Tavily 的关键部分。高德返回的是结构化 POI,适合作业演示;对国内网络也更友好。

importosimportrequests AMAP_KEY=os.environ.get("AMAP_KEY","")defget_attraction(city:str,weather:str)->str:""" 用高德地图 POI 查询景点;根据天气选择更合适的 POI 关键词。 不访问 Google/Brave/Yahoo/Tavily。 """w=(weatheror"").lower()bad_weather=any(kinwforkin["smok","haze","fog","pm2","霾","雾","烟","rain","雨","snow","雪","storm","雷"])hot=any(kinwforkin["hot","heat","高温","炎热","晴热"])ifbad_weather:keywords="博物馆"advice="当前天气不佳,优先推荐室内场馆。"elifhot:keywords="水上乐园"advice="当前偏热,优先推荐水上/室内或避暑场所。"else:keywords="公园、名胜古迹"advice="天气总体适合出行,优先推荐户外公园/景点。"ifnotAMAP_KEY:return(f"{city}当前天气为{weather}。未配置 AMAP_KEY,临时建议:""天气差选博物馆/美术馆/图书馆;天气好选城市公园/地标/历史街区。")try:r=requests.get("https://restapi.amap.com/v3/place/text",params={"key":AMAP_KEY,"keywords":keywords,"city":city,"citylimit":"true","offset":5,"page":1,"extensions":"base","output":"json",},timeout=8,)r.raise_for_status()data=r.json()ifdata.get("status")!="1":return(f"高德POI查询失败:{data.get('info')},infocode={data.get('infocode')}。"f"临时建议:{advice}也可选择{city}本地标志性景点。")pois=data.get("pois",[])[:5]ifnotpois:returnf"{city}当前天气为{weather}。高德未返回{keywords}结果。{advice}"lines=[]forpinpois:name=p.get("name","未知")addr=p.get("address","地址未知")ifisinstance(addr,list):addr=""lines.append(f"-{name}{addr}")returnf"{city}当前天气为{weather}{advice}\n根据高德POI推荐:\n"+"\n".join(lines)exceptExceptionase:returnf"{city}当前天气为{weather}。高德POI查询异常:{type(e).__name__}:{e}。临时建议:{advice}"available_tools={"get_weather":get_weather,"get_attraction":get_attraction,}

4.4 兼容 OpenAI 接口的大模型客户端

fromopenaiimportOpenAIclassOpenAICompatibleClient:"""调用任何兼容 OpenAI 接口的 LLM 服务。"""def__init__(self,model:str,api_key:str,base_url:str):self.model=model self.client=OpenAI(api_key=api_key,base_url=base_url)defgenerate(self,prompt:str,system_prompt:str)->str:print("正在调用大语言模型...")try:response=self.client.chat.completions.create(model=self.model,messages=[{'role':'system','content':system_prompt},{'role':'user','content':prompt}],stream=False)print("大语言模型响应成功。")returnresponse.choices[0].message.contentexceptExceptionase:print(f"调用LLM API时发生错误:{e}")return"错误:调用语言模型服务时出错。"

配置时改成环境变量读取:

importos API_KEY=os.environ.get("MODELSCOPE_API_KEY","")BASE_URL="https://api-inference.modelscope.cn/v1"MODEL_ID="你的课程模型ID"# 以课程/平台实际可用模型为准ifnotAPI_KEY:raiseValueError("请先设置 MODELSCOPE_API_KEY")llm=OpenAICompatibleClient(model=MODEL_ID,api_key=API_KEY,base_url=BASE_URL)

4.5 主循环与解析器加固

教程原版有两个容易踩坑的点:

  • Action: (.*)re.DOTALL可能把 Action 后面的多行内容一起吞掉;
  • re.match(r"Finish\[(.*)\]", action_str)默认不匹配换行,模型把最终答案写成多行时会得到None,进而触发AttributeError: 'NoneType' object has no attribute 'group'

我改成按行取 Action,同时对Finish[...]使用re.DOTALL

importre user_prompt="你好,请帮我查询一下今天银川的天气,然后根据天气推荐一个合适的旅游景点。"prompt_history=[f"用户请求:{user_prompt}"]print(f"用户输入:{user_prompt}\n"+"="*40)foriinrange(5):print(f"--- 循环{i+1}---\n")full_prompt="\n".join(prompt_history)llm_output=llm.generate(full_prompt,system_prompt=AGENT_SYSTEM_PROMPT)match=re.search(r'(Thought:.*?Action:.*?)(?=\n\s*(?:Thought:|Action:|Observation:)|\Z)',llm_output,re.DOTALL)ifmatch:truncated=match.group(1).strip()iftruncated!=llm_output.strip():llm_output=truncatedprint("已截断多余的 Thought-Action 对")print(f"模型输出:\n{llm_output}\n")prompt_history.append(llm_output)action_match=re.search(r"^Action:\s*(.+)$",llm_output,re.MULTILINE)ifnotaction_match:observation="错误: 未能解析到 Action 字段。请确保你的回复严格遵循 'Thought: ... Action: ...' 的格式。"prompt_history.append(f"Observation:{observation}")continueaction_str=action_match.group(1).strip()ifaction_str.startswith("Finish"):m=re.search(r"Finish\[(.*)\]\s*$",llm_output,re.DOTALL)ifnotm:prompt_history.append("Observation: 错误: Finish 格式不完整,请使用 Action: Finish[最终答案]。")continuefinal_answer=m.group(1).strip()print(f"任务完成,最终答案:{final_answer}")breaktool_match=re.search(r"(\w+)\((.*)\)",action_str,re.DOTALL)ifnottool_match:observation=f"错误:无法解析工具调用 '{action_str}'"else:tool_name=tool_match.group(1)args_str=tool_match.group(2)kwargs=dict(re.findall(r"(\w+)\s*=\s*['\"]([^'\"]*)['\"]",args_str))iftool_nameinavailable_tools:try:observation=available_tools[tool_name](**kwargs)exceptExceptionase:observation=f"错误:工具{tool_name}执行失败 -{type(e).__name__}:{e}"else:observation=f"错误:未定义的工具 '{tool_name}'"print(f"Observation:{observation}\n"+"="*40)prompt_history.append(f"Observation:{observation}")

5. 运行结果

我将城市改成了“银川”,实际运行输出如下:

用户输入: 你好,请帮我查询一下今天银川的天气,然后根据天气推荐一个合适的旅游景点。 ======================================== --- 循环 1 --- 模型输出: Thought: 用户需要查询今天银川的天气,并根据天气推荐合适的旅游景点。我先调用天气工具获取银川实时天气。 Action: get_weather(city="银川") Observation: 银川当前天气:Clear ,气温22摄氏度 ======================================== --- 循环 2 --- 模型输出: Thought: 已获取银川实时天气为晴朗(Clear),气温22摄氏度。接下来根据城市和天气搜索推荐的旅游景点。 Action: get_attraction(city="银川", weather="Clear") Observation: 银川 当前天气为 Clear。天气总体适合出行,优先推荐户外公园/景点。 根据高德POI推荐: - 西夏陵国家考古遗址公园:西夏陵景区内 - 贺兰山岩画遗址公园:贺兰山岩画内(南侧) - 明长城-银川段遗址公园:明长城银川段西北侧 ======================================== --- 循环 3 --- 模型输出: Thought: 已获取银川天气和适合晴朗天气的景点推荐,可以回答用户。 Action: Finish[今天银川天气为晴朗(Clear),气温约22℃,非常适合户外出行。推荐你去“西夏陵国家考古遗址公园”……] 任务完成,最终答案: 今天银川天气为晴朗(Clear),气温约22℃,非常适合户外出行。推荐你去“西夏陵国家考古遗址公园”,可以在晴好天气下参观西夏历史文化遗址、感受户外风光。另外,贺兰山岩画遗址公园和明长城-银川段遗址公园也很适合今天游览。

可以看到,虽然底层工具从 Tavily 换成了高德 POI,但智能体的运行范式没有变:仍然是先规划、再调用工具、读取 Observation、最后合成答案。

6. 踩坑记录

  1. Tavily 注册失败不代表作业做不了。智能体重点是“循环”和“工具协议”,搜索源可以替换。替换成高德后,反而更适合国内课堂环境。
  2. 高德 Key 类型要选 Web服务。如果选成 JS API、Android 或 iOS,常见报错是INVALID_USER_KEYUSERKEY_PLAT_NOMATCH
  3. 不要把真实 Key 写进代码或文章。哪怕是作业截图,也建议打码;仓库提交前用环境变量读取。
  4. Finish[...]要支持多行。模型最终答案经常会写成多行,如果正则不支持换行,会在最后一步崩溃。
  5. 最大循环次数必须保留。没有上限时,一旦工具持续失败或模型不守格式,智能体可能反复重试,浪费 Token。

7. 总结

这次作业让我对第一章的概念有了更直观的理解:LLM 智能体不是“单次问答”,而是一个由Thought-Action-Observation驱动的闭环系统。Thought负责规划,Action负责调用工具,Observation把环境反馈重新交给模型。只要这个协议稳定,底层工具是可以替换的:天气可以用wttr.in,景点搜索可以从 Tavily 换成高德 POI,甚至换成地图、票务或本地知识库。

后续如果继续完善,我计划加两个能力:一是“记忆”,让智能体记住用户偏好,比如喜欢历史文化还是自然风光;二是“失败熔断”,同一个工具连续失败两次后禁止原样重试,直接基于已有信息给出结论并说明限制。

参考

  • Datawhale《Hello-Agents》第一章:初识智能体
  • 高德开放平台 Web 服务 POI 搜索接口
  • wttr.in 天气查询服务

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询