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 桌面应用程序。

技术栈:

组件版本作用
LangChain1.3.14Agent 框架
langchain-openai1.4.1OpenAI 兼容接口
DeepSeek APIdeepseek-chat大语言模型
tkinter内置GUI 界面
PyInstaller6.21.0EXE 打包

最终产物:

  • 命令行脚本: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_urlmodel
DeepSeekhttps://api.deepseek.comdeepseek-chat
OpenAIhttps://api.openai.com/v1gpt-4o
硅基流动https://api.siliconflow.cn/v1deepseek-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.exeEXE 可执行文件路径
起始位置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" 字体
快捷方式名称不是 AgentPowerShell 脚本写错路径检查 $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 垂直技术社区,欢迎活跃、内容共建。

更多推荐