LangChain 天气助手从脚本到桌面应用:5 步打包你的第一个 AI Agent EXE

摘要:本文手把手教你从零构建一个基于 LangChain + DeepSeek 的天气查询智能体,涵盖命令行版本、GUI 可视化版本,最终通过 PyInstaller 打包为单文件 EXE,并创建桌面快捷方式。完整覆盖:环境准备、模型配置、工具定义、Agent 构建、tkinter GUI 开发、PyInstaller 打包、桌面快捷方式创建。每个步骤配有可直接运行的完整代码。


目录

  • 一、项目概述
  • 二、环境准备
  • 三、构建命令行 Agent
    • 3.1 定义工具
    • 3.2 配置模型
    • 3.3 创建 Agent
    • 3.4 运行测试
  • 四、升级为 GUI 版本
    • 4.1 GUI 设计思路
    • 4.2 完整 GUI 代码
    • 4.3 GUI 运行效果
  • 五、PyInstaller 打包为 EXE
    • 5.1 安装 PyInstaller
    • 5.2 打包命令详解
    • 5.3 打包常见问题
  • 六、创建桌面快捷方式
    • 6.1 PowerShell 脚本创建
    • 6.2 快捷方式属性配置
  • 七、完整项目结构
  • 八、使用说明
  • 九、踩坑清单
  • 十、总结
  • 封面图提示词

一、项目概述

本项目基于 LangChain 官方开源的 langchain-skills 项目(https://github.com/langchain-ai/langchain-skills),实现了一个简单的天气查询 Agent,并最终打包为 Windows 桌面应用程序。

技术栈

组件 版本 作用
LangChain 1.3.14 Agent 框架
langchain-openai 1.4.1 OpenAI 兼容接口
DeepSeek API deepseek-chat 大语言模型
tkinter 内置 GUI 界面
PyInstaller 6.21.0 EXE 打包

最终产物

  • 命令行脚本:weather_agent.py
  • GUI 脚本:weather_agent_gui.py
  • 桌面应用:Agent.exe
  • 桌面快捷方式:Agent.lnk

二、环境准备

2.1 安装依赖

pip install langchain langchain-openai

国内镜像加速:

pip install langchain langchain-openai -i https://pypi.tuna.tsinghua.edu.cn/simple

2.2 配置 API Key

本项目使用 DeepSeek 的 OpenAI 兼容接口:

model = ChatOpenAI(
    model="deepseek-chat",
    api_key="sk-f3c7557e9fc54541802fd638de03bbeb",
    base_url="https://api.deepseek.com",
    temperature=0.7,
)

你也可以替换为其他兼容 OpenAI 格式的模型

服务商 base_url model
DeepSeek https://api.deepseek.com deepseek-chat
OpenAI https://api.openai.com/v1 gpt-4o
硅基流动 https://api.siliconflow.cn/v1 deepseek-ai/DeepSeek-V3

三、构建命令行 Agent

3.1 定义工具

def get_weather(city: str) -> str:
    """Get weather for a given city."""
    return f"It's always sunny in {city}!"

关键点

  • 函数名即工具名
  • docstring 描述工具用途(Agent 通过 docstring 判断何时调用)
  • 参数类型标注帮助 Agent 正确传参

3.2 完整命令行版本代码

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI


def get_weather(city: str) -> str:
    """Get weather for a given city."""
    return f"It's always sunny in {city}!"


# 使用 DeepSeek (OpenAI兼容格式)
model = ChatOpenAI(
    model="deepseek-chat",
    api_key="sk-f3c7557e9fc54541802fd638de03bbeb",
    base_url="https://api.deepseek.com",
    temperature=0.7,
)

agent = create_agent(
    model=model,
    tools=[get_weather],
    system_prompt="You are a helpful assistant",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "What's the weather in San Francisco?"}]}
)
print(result["messages"][-1].content)

3.3 运行测试

python weather_agent.py

输出

The weather in San Francisco is always sunny! ☀️

运行流程解析

用户提问:"What's the weather in San Francisco?"
    ↓
Agent 分析:涉及"天气"和"城市"
    ↓
Agent 决定:调用 get_weather 工具,参数 city="San Francisco"
    ↓
工具执行:get_weather("San Francisco") → "It's always sunny in San Francisco!"
    ↓
Agent 整理:把工具返回整理成自然语言回答
    ↓
最终输出:"The weather in San Francisco is always sunny! ☀️"

四、升级为 GUI 版本

4.1 GUI 设计思路

使用 Python 内置的 tkinter 库,无需额外安装:

组件 功能
聊天记录区(ScrolledText) 显示对话历史
输入框(Entry) 用户输入问题
发送按钮 提交问题
快捷按钮 预设常用问题
状态栏 显示运行状态

4.2 完整 GUI 代码

import tkinter as tk
from tkinter import ttk, scrolledtext
import threading
from datetime import datetime

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI


def get_weather(city: str) -> str:
    """Get weather for a given city."""
    return f"It's always sunny in {city}!"


def get_current_time() -> str:
    """Get the current date and time."""
    now = datetime.now()
    return f"Current time: {now.strftime('%Y-%m-%d %H:%M:%S')}"


def calculate(expression: str) -> str:
    """Calculate a mathematical expression."""
    try:
        result = eval(expression)
        return f"Result: {result}"
    except:
        return "Invalid expression"


class WeatherAgentApp:
    def __init__(self, root):
        self.root = root
        self.root.title("AI 智能助手 - Weather Agent")
        self.root.geometry("800x600")
        self.root.minsize(700, 500)
        
        # 初始化模型和Agent
        self.init_agent()
        
        # 创建UI
        self.create_ui()
        
        # 欢迎消息
        self.add_system_message("AI 智能助手已启动!您可以问我:\n")
        self.add_system_message("1. 天气查询:\"北京天气怎么样?\"\n")
        self.add_system_message("2. 时间查询:\"现在几点了?\"\n")
        self.add_system_message("3. 数学计算:\"计算 25 * 4 + 10\"\n")
    
    def init_agent(self):
        """初始化AI模型和Agent"""
        self.model = ChatOpenAI(
            model="deepseek-chat",
            api_key="sk-f3c7557e9fc54541802fd638de03bbeb",
            base_url="https://api.deepseek.com",
            temperature=0.7,
        )
        
        self.agent = create_agent(
            model=self.model,
            tools=[get_weather, get_current_time, calculate],
            system_prompt="You are a helpful assistant.",
        )
        self.agent_ready = True
    
    def create_ui(self):
        """创建用户界面"""
        # 顶部标题
        header_frame = ttk.Frame(self.root, padding="10")
        header_frame.pack(fill=tk.X)
        
        title_label = ttk.Label(
            header_frame, 
            text="AI 智能助手", 
            font=("Microsoft YaHei", 18, "bold")
        )
        title_label.pack()
        
        # 聊天显示区域
        chat_frame = ttk.Frame(self.root, padding="10")
        chat_frame.pack(fill=tk.BOTH, expand=True)
        
        self.chat_display = scrolledtext.ScrolledText(
            chat_frame,
            wrap=tk.WORD,
            font=("Microsoft YaHei", 11),
            bg="#f5f5f5",
            padx=10,
            pady=10,
            state=tk.DISABLED
        )
        self.chat_display.pack(fill=tk.BOTH, expand=True)
        
        # 配置标签样式
        self.chat_display.tag_configure("user", foreground="#0066cc", font=("Microsoft YaHei", 11, "bold"))
        self.chat_display.tag_configure("assistant", foreground="#009933", font=("Microsoft YaHei", 11, "bold"))
        self.chat_display.tag_configure("system", foreground="#666666", font=("Microsoft YaHei", 10))
        
        # 输入区域
        input_frame = ttk.Frame(self.root, padding="10")
        input_frame.pack(fill=tk.X, side=tk.BOTTOM)
        
        self.input_var = tk.StringVar()
        self.input_entry = ttk.Entry(
            input_frame,
            textvariable=self.input_var,
            font=("Microsoft YaHei", 12)
        )
        self.input_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
        self.input_entry.bind("<Return>", lambda e: self.send_message())
        
        self.send_btn = ttk.Button(
            input_frame,
            text="发送",
            command=self.send_message,
            width=10
        )
        self.send_btn.pack(side=tk.RIGHT)
        
        # 快捷按钮区域
        quick_frame = ttk.Frame(self.root, padding="10")
        quick_frame.pack(fill=tk.X, side=tk.BOTTOM)
        
        quick_label = ttk.Label(quick_frame, text="快捷提问:")
        quick_label.pack(side=tk.LEFT)
        
        quick_questions = [
            "北京天气怎么样?",
            "现在几点了?",
            "计算 25 * 4",
            "你好"
        ]
        
        for q in quick_questions:
            btn = ttk.Button(
                quick_frame,
                text=q[:8] + "..." if len(q) > 8 else q,
                command=lambda question=q: self.ask_quick(question)
            )
            btn.pack(side=tk.LEFT, padx=5)
        
        # 状态栏
        self.status_var = tk.StringVar(value="就绪")
        status_bar = ttk.Label(
            self.root,
            textvariable=self.status_var,
            font=("Microsoft YaHei", 9),
            foreground="gray",
            anchor=tk.W
        )
        status_bar.pack(fill=tk.X, side=tk.BOTTOM, padx=10, pady=2)
        
        # 聚焦输入框
        self.input_entry.focus()
    
    def add_message(self, role, content):
        """添加消息到聊天显示区域"""
        self.chat_display.config(state=tk.NORMAL)
        timestamp = datetime.now().strftime("%H:%M:%S")
        
        if role == "user":
            self.chat_display.insert(tk.END, f"\n[{timestamp}] ", "system")
            self.chat_display.insert(tk.END, "你:", "user")
            self.chat_display.insert(tk.END, f" {content}\n", "")
        elif role == "assistant":
            self.chat_display.insert(tk.END, f"[{timestamp}] ", "system")
            self.chat_display.insert(tk.END, "AI:", "assistant")
            self.chat_display.insert(tk.END, f" {content}\n", "")
        elif role == "system":
            self.chat_display.insert(tk.END, f"[{timestamp}] ", "system")
            self.chat_display.insert(tk.END, f"{content}\n", "system")
        
        self.chat_display.see(tk.END)
        self.chat_display.config(state=tk.DISABLED)
    
    def add_system_message(self, content):
        self.add_message("system", content)
    
    def send_message(self):
        """发送用户消息"""
        question = self.input_var.get().strip()
        if not question:
            return
        
        self.input_var.set("")
        self.add_message("user", question)
        
        # 在后台线程中处理AI响应
        self.status_var.set("AI思考中...")
        self.send_btn.config(state=tk.DISABLED)
        self.input_entry.config(state=tk.DISABLED)
        
        thread = threading.Thread(target=self.process_ai_response, args=(question,))
        thread.daemon = True
        thread.start()
    
    def process_ai_response(self, question):
        """在后台线程中处理AI响应"""
        try:
            result = self.agent.invoke(
                {"messages": [{"role": "user", "content": question}]}
            )
            answer = result["messages"][-1].content
            self.root.after(0, lambda: self.add_message("assistant", answer))
        except Exception as e:
            self.root.after(0, lambda: self.add_message("system", f"错误:{str(e)}"))
        finally:
            self.root.after(0, self.reset_ui_state)
    
    def reset_ui_state(self):
        """重置UI状态"""
        self.status_var.set("就绪")
        self.send_btn.config(state=tk.NORMAL)
        self.input_entry.config(state=tk.NORMAL)
        self.input_entry.focus()
    
    def ask_quick(self, question):
        """快捷提问"""
        self.input_var.set(question)
        self.send_message()


def main():
    root = tk.Tk()
    app = WeatherAgentApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()

4.3 运行 GUI 版本

python weather_agent_gui.py

界面包含:

  • 顶部标题栏
  • 聊天记录区(带时间戳,区分用户和AI)
  • 输入框 + 发送按钮
  • 快捷提问按钮(预设4个常用问题)
  • 状态栏显示运行状态

五、PyInstaller 打包为 EXE

5.1 安装 PyInstaller

pip install pyinstaller

5.2 打包命令详解

cd /f/hmc/wzsc
pyinstaller --onefile --windowed --name "Agent" --clean weather_agent_gui.py

参数说明

参数 作用
--onefile 打包为单文件 exe(所有依赖合并)
--windowed 不显示控制台窗口(GUI 应用必备)
--name "Agent" 生成的 exe 文件名
--clean 清理临时文件

5.3 打包常见问题

问题 原因 解决方案
打包后 exe 很大(50MB+) 包含了 Python 解释器和所有依赖 正常,使用 --onefile 就是单文件
缺少 tkinter 支持 PyInstaller 未正确打包 tkinter 使用标准 Python 安装,不要用 Anaconda
运行时报缺失 DLL 系统依赖不完整 在相同系统环境打包,或用 --hidden-import
启动很慢 --onefile 需要先解压到临时目录 正常现象,可用 --onedir 替代

六、创建桌面快捷方式

6.1 PowerShell 脚本创建

创建快捷方式脚本 create_shortcut.ps1

$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut('C:\Users\WIT_User\Desktop\Agent.lnk')
$Shortcut.TargetPath = 'F:\hmc\wzsc\dist\Agent.exe'
$Shortcut.WorkingDirectory = 'F:\hmc\wzsc\dist'
$Shortcut.IconLocation = 'F:\hmc\wzsc\dist\Agent.exe,0'
$Shortcut.Description = 'AI Weather Agent'
$Shortcut.Save()
Write-Host 'Desktop shortcut created successfully'

运行脚本:

powershell -NoProfile -ExecutionPolicy Bypass -File create_shortcut.ps1

6.2 快捷方式属性配置

快捷方式创建后包含以下属性:

属性 说明
目标 F:\hmc\wzsc\dist\Agent.exe EXE 可执行文件路径
起始位置 F:\hmc\wzsc\dist 工作目录
图标 Agent.exe,0 使用 EXE 自带图标
描述 AI Weather Agent 鼠标悬停提示

七、完整项目结构

F:\hmc\wzsc\
├── weather_agent.py              # 命令行版本
├── weather_agent_gui.py          # GUI 版本
├── Agent.spec                    # PyInstaller 配置文件
├── create_shortcut.ps1           # 快捷方式创建脚本
├── dist\
│   └── Agent.exe                 # 打包后的桌面应用
└── build\                        # PyInstaller 构建临时文件

八、使用说明

8.1 命令行使用

python weather_agent.py

修改问题:编辑 weather_agent.py 第 24 行的 content

8.2 GUI 使用

python weather_agent_gui.py
  • 在输入框输入问题,按回车发送
  • 或点击快捷按钮快速提问
  • 查看聊天记录中的 AI 回复

8.3 桌面应用使用

双击桌面的 Agent 快捷方式:

  1. 启动无需 Python 环境
  2. 自动连接 DeepSeek API
  3. 输入问题后等待 AI 回复
  4. 支持天气、时间、计算查询

九、踩坑清单

描述 解决方案
网络不通 国内访问 DeepSeek 或 OpenAI 超时 换用国内镜像接口(硅基流动等)
API Key 过期 DeepSeek Key 欠费或失效 检查 Key 状态,或替换为新 Key
tkinter 缺失 Python 安装未包含 tkinter 重新安装标准 Python(勾选 tcl/tk)
打包后无图标 PyInstaller 默认无图标 --icon=icon.ico 指定自定义图标
快捷方式路径错误 移动项目后快捷方式失效 右键快捷方式 → 属性 → 修改目标路径
Agent 不调用工具 docstring 写得不够清楚 详细描述工具用途和参数
GUI 界面卡顿 模型调用在主线程阻塞 使用 threading.Thread 后台执行
打包后报缺失模块 PyInstaller 未检测到隐式导入 --hidden-import module_name 补充
中文显示乱码 tkinter 字体不支持中文 指定 "Microsoft YaHei" 字体
快捷方式名称不是 Agent PowerShell 脚本写错路径 检查 $Shortcut.TargetPath 变量

十、总结

本文完整展示了从命令行脚本到桌面应用的完整流程:

  1. 命令行版本(20行代码):定义工具 → 配置模型 → 创建 Agent → 运行
  2. GUI 版本(200+行代码):添加 tkinter 界面、聊天记录、快捷按钮、状态栏
  3. EXE 打包(一行命令):PyInstaller --onefile --windowed 一键打包
  4. 桌面快捷方式(PowerShell 脚本):自动创建 Agent.lnk

核心原理回顾

  • Agent 通过 docstring 判断何时调用工具
  • create_agent 只需要模型 + 工具列表 + 系统提示词
  • tkinter 是 Python 内置 GUI,无需额外安装
  • PyInstaller --onefile 生成独立可执行文件
阶段 代码量 复杂度 适用场景
命令行 ~30行 快速验证、自动化脚本
GUI ~200行 日常工具、内部使用
EXE 无代码 交付用户、无 Python 环境


如果觉得这篇文章对你有帮助,欢迎点赞收藏。你在打包 Python 应用时遇到了什么问题?评论区交流。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐