1. 引言

今天要分享的是一个用 Python Tkinter 写成的桌面小工具:ChatRobot。它把多个 AI 能力集合到一个界面里,主要包含三大功能:

  • 接入 DeepSeek 大模型,实现智能对话;
  • 调用 wttr.in 接口,实现城市天气查询;
  • 调用 Tavily Search API,根据城市和天气推荐旅游景点。

其中,智能体(Agent)部分的设计思路参考了 Datawhale 出品的《初识智能体》教程(https://hello-agents.datawhale.cc/#/./chapter1/第一章%20初识智能体),该教程系统讲解了智能体的核心概念、工作流程与工具调用机制,帮助我更好地理解了如何把大模型、工具调用与业务逻辑组合成一个可用的智能体应用。

  • 接入 DeepSeek 大模型,实现智能对话;
  • 调用 wttr.in 接口,实现城市天气查询;
  • 调用 Tavily Search API,根据城市和天气推荐旅游景点。

这个项目很适合作为 Python GUI + AI 接口调用的练习,因为它覆盖了事件驱动编程、HTTP 请求、第三方 API 接入等常见内容。

另外,原始代码里把 DeepSeek 和 Tavily 的密钥直接写在了源码中,这样非常不安全。本文在介绍项目的同时,已经把密钥改成从环境变量读取,避免把真实密钥提交到博客、代码仓库或分享给别人时泄露。

2. 项目功能总览

本项目的主窗口包含几个按钮:

  • 聊天:输入问题,调用 DeepSeek 返回回答;
  • 查询天气:输入城市,调用 wttr.in 返回天气;
  • 旅游攻略:输入城市和天气,调用 Tavily 搜索旅游推荐;
  • 退出:关闭程序。

整体界面如下:

ChatRobot
请输入您的问题:[____________]

[ 发送 ]      [ 退出 ]
[ 查询天气 ] [ 旅游攻略 ]

聊天窗口是标准 ChatRobot 风格,天气和旅游功能则使用 Toplevel 弹出子窗口,方便独立操作。

3. 环境准备

在运行项目前,需要安装以下依赖:

pip install openai tavily-python requests

说明:

  • tkinter 是 Python 标准库,一般情况下无需额外安装;
  • openai 用于调用 DeepSeek 的 OpenAI 兼容接口;
  • tavily-python 用于调用 Tavily 搜索能力;
  • requests 用于调用 wttr.in 天气接口。

4. 密钥安全:不要硬编码

原代码中出现了类似下面这样的硬编码密钥,这是需要重点避免的问题:

client = OpenAI(
    api_key="sk-你的真实密钥",
    base_url="https://api.deepseek.com"
)

一旦把包含真实密钥的代码提交到 GitHub、粘贴到博客,或者分享给他人,密钥就可能被盗用,带来费用损失或安全风险。

更安全的做法是把密钥放进环境变量,然后在代码中通过 os.environ.get() 读取:

import os

# 方式一:从环境变量读取(推荐)
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY")
TAVILY_API_KEY = os.environ.get("TAVILY_API_KEY")

# 方式二:从配置文件读取(可选)
# 在项目根目录创建 config.ini 文件,内容如下:
# [api]
# deepseek_api_key = 你的DeepSeek密钥
# tavily_api_key = 你的Tavily密钥
#
# 然后使用 configparser 读取:
# import configparser
# config = configparser.ConfigParser()
# config.read("config.ini")
# DEEPSEEK_API_KEY = config.get("api", "deepseek_api_key")
# TAVILY_API_KEY = config.get("api", "tavily_api_key")

在运行程序之前,先设置环境变量。

Windows PowerShell:

$env:DEEPSEEK_API_KEY="你的DeepSeek密钥"
$env:TAVILY_API_KEY="你的Tavily密钥"

Windows CMD:

set DEEPSEEK_API_KEY=你的DeepSeek密钥
set TAVILY_API_KEY=你的Tavily密钥

macOS 或 Linux:

export DEEPSEEK_API_KEY="你的DeepSeek密钥"
export TAVILY_API_KEY="你的Tavily密钥"

这样,代码中就不会出现真实密钥,密钥只保存在本地环境中,分享代码时也更安全。

5. 界面搭建:主窗口与功能入口

Tkinter 代码通常从创建根窗口开始。我们设置窗口标题、大小、并禁止调整尺寸:

import tkinter as tk

root = tk.Tk()
root.title("ChatRobot")
root.geometry("800x800+100+200")
root.resizable(False, False)

聊天区域包含一个输入框和一个多行文本框,使用 grid 布局:

label = tk.Label(root, text="请输入您的问题:", font=("Arial", 12, "bold"))
label.grid(row=0, column=0, padx=10, pady=10, sticky=tk.W)

entry = tk.Entry(root, font=("Arial", 15, "bold"), width=51)
entry.grid(row=0, column=1, padx=10, pady=10, sticky=tk.W)

text = tk.Text(root, height=18, width=64, font=("Arial", 15, "bold"))
text.grid(row=1, column=0, columnspan=2, padx=10, pady=10, sticky=tk.W)

底部再放上发送、退出、查询天气、旅游攻略四个按钮,把聊天、天气和旅游三个功能串联起来。

6. 聊天功能:接入 DeepSeek

点击“发送”按钮后,程序读取输入框内容,然后把消息添加到聊天区域。这里需要注意:如果输入为空,应直接结束函数,避免后续使用未初始化的 client 变量。

def send_message():
    query = entry.get().strip()
    if not query:
        text.insert(tk.END, "系统:请输入您的问题!\n")
        return

    text.insert(tk.END, "你:" + query + "\n")
    entry.delete(0, tk.END)
    btn1.config(state=tk.DISABLED, text="正在思考中...")
    root.update()

    if not DEEPSEEK_API_KEY:
        text.insert(tk.END, "系统:未配置 DEEPSEEK_API_KEY 环境变量。\n")
        btn1.config(state=tk.NORMAL, text="发送")
        return

    try:
        client = OpenAI(
            api_key=DEEPSEEK_API_KEY,
            base_url="https://api.deepseek.com",
            timeout=30.0,  # 设置请求超时时间
        )
        response = client.chat.completions.create(
            model="deepseek-v4-pro",
            messages=[
                {"role": "system", "content": "You are a helpful assistant"},
                {"role": "user", "content": query},
            ],
            stream=False,
            reasoning_effort="high",
            extra_body={"thinking": {"type": "enabled"}},
        )
        ans = response.choices[0].message.content
        text.insert(tk.END, "机器人:" + ans + "\n")
    except openai.APIConnectionError as e:
        # 网络连接失败或超时
        text.insert(tk.END, f"机器人:网络连接失败,请检查网络后重试。\n详细信息:{e}\n")
    except openai.AuthenticationError as e:
        # API 密钥无效或已过期
        text.insert(tk.END, f"机器人:API 密钥无效或已过期,请检查 DEEPSEEK_API_KEY 配置。\n详细信息:{e}\n")
    except openai.RateLimitError as e:
        # 请求频率超限或余额不足
        text.insert(tk.END, f"机器人:请求过于频繁或账户余额不足,请稍后再试。\n详细信息:{e}\n")
    except openai.APIStatusError as e:
        # 服务端返回 4xx/5xx 错误
        text.insert(tk.END, f"机器人:DeepSeek 服务返回错误(状态码 {e.status_code})。\n详细信息:{e}\n")
    except Exception as e:
        # 兜底异常处理
        text.insert(tk.END, f"机器人:发生未知错误 - {e}\n")
    finally:
        btn1.config(state=tk.NORMAL, text="发送")

这里使用了 DeepSeek 的 OpenAI 兼容接口,base_url 指向 https://api.deepseek.com。密钥通过环境变量读取,不再出现在源码中。

针对 send_message 函数,我们做了更细致的异常分类:

  • 网络连接失败openai.APIConnectionError,通常是断网、DNS 解析失败或请求超时;
  • 密钥无效openai.AuthenticationError,说明 DEEPSEEK_API_KEY 配置错误或已过期;
  • 请求限流openai.RateLimitError,可能是请求过于频繁或账户余额不足;
  • 服务端错误openai.APIStatusError,DeepSeek 服务返回了 4xx/5xx 状态码;
  • 未知错误:兜底的 Exception,防止程序因未预料的异常而崩溃。

同时,在创建 OpenAI 客户端时增加了 timeout=30.0,避免请求长时间挂起导致界面卡死。

7. 天气查询:调用 wttr.in

天气功能放在一个新的 Toplevel 窗口中。用户输入城市后,程序请求:

https://wttr.in/城市名?format=j1

返回 JSON 后,提取当前天气状况和温度,并插到文本框中显示:

def get_weather():
    city = entry1.get().strip()
    if not city:
        text2.insert(tk.END, "机器人:请输入城市名称\n")
        return

    btn3.config(state=tk.DISABLED, text="正在查询...")
    top1.update()

    url = f"https://wttr.in/{city}?format=j1"

    try:
        response = requests.get(url, timeout=10)  # 设置 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"]

        ans = f"{city} 当前天气:{weather_desc},气温 {temp_c} 摄氏度"
        text2.insert(tk.END, ans + "\n")
    except requests.exceptions.Timeout as e:
        # 请求超时
        text2.insert(tk.END, f"错误:查询天气超时,请稍后重试。\n详细信息:{e}\n")
    except requests.exceptions.ConnectionError as e:
        # 网络连接失败
        text2.insert(tk.END, f"错误:无法连接到天气服务,请检查网络。\n详细信息:{e}\n")
    except requests.exceptions.HTTPError as e:
        # 服务端返回 4xx/5xx 错误
        status_code = e.response.status_code if e.response is not None else "未知"
        text2.insert(tk.END, f"错误:天气服务返回错误(状态码 {status_code})。\n详细信息:{e}\n")
    except requests.exceptions.JSONDecodeError as e:
        # JSON 解析失败
        text2.insert(tk.END, f"错误:天气服务返回的数据格式异常,无法解析。\n详细信息:{e}\n")
    except (KeyError, IndexError) as e:
        # 数据结构不符合预期,通常是城市名称无效
        text2.insert(tk.END, f"错误:解析天气数据失败,可能是城市名称无效。\n详细信息:{e}\n")
    except Exception as e:
        # 兜底异常处理
        text2.insert(tk.END, f"错误:发生未知错误 - {e}\n")
    finally:
        btn3.config(state=tk.NORMAL, text="查询")

这里同时处理了网络异常和数据解析异常。针对 get_weather 函数,我们做了更细致的异常分类:

  • 请求超时requests.exceptions.Timeout,网络响应超过 10 秒;
  • 连接失败requests.exceptions.ConnectionError,无法连接到 wttr.in 服务;
  • HTTP 错误requests.exceptions.HTTPError,服务端返回了 4xx/5xx 状态码;
  • JSON 解析失败requests.exceptions.JSONDecodeError,返回内容不是合法的 JSON;
  • 数据结构异常KeyErrorIndexError,通常是城市名称无效导致返回的数据结构不符合预期;
  • 未知错误:兜底的 Exception

同时,在 requests.get() 中增加了 timeout=10,避免请求长时间挂起导致界面卡死。

8. 旅游攻略:调用 Tavily 搜索

旅游查询窗口需要用户输入城市和天气。随后程序构造一个用于搜索的自然语言问题,交给 Tavily 搜索:

query = f"'{city}' 在'{weather}'天气下最值得去的旅游景点推荐及理由"

tavily = TavilyClient(api_key=TAVILY_API_KEY)
response = tavily.search(
    query=query,
    search_depth="basic",
    include_answer=True
)

当 Tavily 返回综合回答时,直接显示 response["answer"];如果没有综合性回答,则把原始结果格式化成列表展示:

if response.get("answer"):
    text3.insert(tk.END, response["answer"] + "\n")
    return

formatted_results = []
for result in response.get("results", []):
    formatted_results.append(f"- {result['title']}: {result['content']}")

if not formatted_results:
    text3.insert(tk.END, "抱歉,没有找到相关的旅游景点推荐。\n")
    return

search_results = "根据搜索,为您找到以下信息:\n" + "\n".join(formatted_results)
text3.insert(tk.END, search_results + "\n")

这个设计让旅游推荐既有一个总览性回答,也有多个可继续展开的信息来源,体验更好。

9. 完整代码

下面是整理后的完整代码。密钥已经全部改成通过环境变量读取,不会再泄露真实值:

import os
import tkinter as tk

import requests
from openai import OpenAI
from tavily import TavilyClient

# 从环境变量读取密钥,避免硬编码泄露
DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY")
TAVILY_API_KEY = os.environ.get("TAVILY_API_KEY")

root = tk.Tk()
root.title("ChatRobot")
root.geometry("800x800+100+200")
root.resizable(False, False)

label = tk.Label(root, text="请输入您的问题:", font=("Arial", 12, "bold"))
label.grid(row=0, column=0, padx=10, pady=10, sticky=tk.W)

entry = tk.Entry(root, font=("Arial", 15, "bold"), width=51)
entry.grid(row=0, column=1, padx=10, pady=10, sticky=tk.W)

text = tk.Text(root, height=18, width=64, font=("Arial", 15, "bold"))
text.grid(row=1, column=0, columnspan=2, padx=10, pady=10, sticky=tk.W)


def send_message():
    query = entry.get().strip()
    if not query:
        text.insert(tk.END, "系统:请输入您的问题!\n")
        return

    text.insert(tk.END, "你:" + query + "\n")
    entry.delete(0, tk.END)
    btn1.config(state=tk.DISABLED, text="正在思考中...")
    root.update()

    if not DEEPSEEK_API_KEY:
        text.insert(tk.END, "系统:未配置 DEEPSEEK_API_KEY 环境变量。\n")
        btn1.config(state=tk.NORMAL, text="发送")
        return

    try:
        client = OpenAI(
            api_key=DEEPSEEK_API_KEY,
            base_url="https://api.deepseek.com"
        )
        response = client.chat.completions.create(
            model="deepseek-v4-pro",
            messages=[
                {"role": "system", "content": "You are a helpful assistant"},
                {"role": "user", "content": query},
            ],
            stream=False,
            reasoning_effort="high",
            extra_body={"thinking": {"type": "enabled"}},
        )
        ans = response.choices[0].message.content
        text.insert(tk.END, "机器人:" + ans + "\n")
    except Exception as e:
        text.insert(tk.END, "机器人:" + str(e) + "\n")
    finally:
        btn1.config(state=tk.NORMAL, text="发送")


def weather():
    top1 = tk.Toplevel(root)
    top1.title("天气查询")
    top1.geometry("800x800+100+200")
    top1.resizable(False, False)

    tk.Label(top1, text="请输入城市名称:", font=("Arial", 12, "bold")).grid(
        row=0, column=0, padx=10, pady=10, sticky=tk.W
    )
    entry1 = tk.Entry(top1, width=50, font=("Arial", 12, "bold"))
    entry1.grid(row=0, column=1, padx=10, pady=10)

    text2 = tk.Text(top1, width=64, height=18, font=("Arial", 15, "bold"))
    text2.grid(row=1, column=0, columnspan=2, padx=10, pady=10)

    def get_weather():
        city = entry1.get().strip()
        if not city:
            text2.insert(tk.END, "机器人:请输入城市名称\n")
            return

        btn3.config(state=tk.DISABLED, text="正在查询...")
        top1.update()

        url = f"https://wttr.in/{city}?format=j1"

        try:
            response = requests.get(url)
            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"]

            ans = f"{city} 当前天气:{weather_desc},气温 {temp_c} 摄氏度"
            text2.insert(tk.END, ans + "\n")
        except requests.exceptions.RequestException as e:
            text2.insert(tk.END, f"错误:查询天气时遇到网络问题 - {e}\n")
        except (KeyError, IndexError) as e:
            text2.insert(tk.END, f"错误:解析天气数据失败,可能是城市名称无效 - {e}\n")
        finally:
            btn3.config(state=tk.NORMAL, text="查询")

    btn3 = tk.Button(top1, text="查询", command=get_weather, font=("Arial", 20, "bold"), width=10)
    btn3.grid(row=2, column=0, padx=10, pady=10)

    tk.Button(top1, text="退出", command=top1.destroy, font=("Arial", 20, "bold"), width=10).grid(
        row=2, column=1, padx=10, pady=10
    )


def travel():
    top2 = tk.Toplevel(root)
    top2.title("旅游查询")
    top2.geometry("800x800+100+200")

    tk.Label(top2, text="请输入城市名称:", font=("Arial", 12, "bold")).grid(
        row=0, column=0, padx=10, pady=10, sticky=tk.W
    )
    entry_city = tk.Entry(top2, width=10, font=("Arial", 12, "bold"))
    entry_city.grid(row=0, column=1, padx=10, pady=10)

    tk.Label(top2, text="请输入天气:", font=("Arial", 12, "bold")).grid(
        row=1, column=0, padx=10, pady=10, sticky=tk.W
    )
    entry_weather = tk.Entry(top2, width=10, font=("Arial", 12, "bold"))
    entry_weather.grid(row=1, column=1, padx=10, pady=10)

    text3 = tk.Text(top2, width=64, height=18, font=("Arial", 15, "bold"))
    text3.grid(row=2, column=0, columnspan=2, padx=10, pady=10)

    def get_attraction():
        city = entry_city.get().strip()
        weather_text = entry_weather.get().strip()

        if not city:
            text3.insert(tk.END, "错误:请输入城市。\n")
            return
        if not weather_text:
            text3.insert(tk.END, "错误:请输入天气。\n")
            return
        if not TAVILY_API_KEY:
            text3.insert(tk.END, "错误:未配置 TAVILY_API_KEY 环境变量。\n")
            return

        btn3.config(state=tk.DISABLED, text="正在查询...")
        top2.update()

        tavily = TavilyClient(api_key=TAVILY_API_KEY)
        query = f"'{city}' 在'{weather_text}'天气下最值得去的旅游景点推荐及理由"

        try:
            response = tavily.search(
                query=query,
                search_depth="basic",
                include_answer=True
            )

            if response.get("answer"):
                text3.insert(tk.END, response["answer"] + "\n")
                return

            formatted_results = []
            for result in response.get("results", []):
                formatted_results.append(f"- {result['title']}: {result['content']}")

            if not formatted_results:
                text3.insert(tk.END, "抱歉,没有找到相关的旅游景点推荐。\n")
                return

            search_results = "根据搜索,为您找到以下信息:\n" + "\n".join(formatted_results)
            text3.insert(tk.END, search_results + "\n")
        except Exception as e:
            text3.insert(tk.END, f"错误:执行 Tavily 搜索时出现问题 - {e}\n")
        finally:
            btn3.config(state=tk.NORMAL, text="查询")

    btn3 = tk.Button(top2, text="查询", command=get_attraction, font=("Arial", 20, "bold"), width=10)
    btn3.grid(row=3, column=0, padx=10, pady=10)

    tk.Button(top2, text="退出", command=top2.destroy, font=("Arial", 20, "bold"), width=10).grid(
        row=3, column=1, padx=10, pady=10
    )


btn1 = tk.Button(root, text="发送", command=send_message, font=("Arial", 20, "bold"), width=10)
btn1.grid(row=2, column=0, padx=10, pady=10)

tk.Button(root, text="退出", command=root.destroy, font=("Arial", 20, "bold"), width=10).grid(
    row=2, column=1, padx=10, pady=10
)

tk.Button(root, text="查询天气", command=weather, font=("Arial", 20, "bold"), bg="lightblue", fg="blue", width=10).grid(
    row=3, column=0, padx=10, pady=10
)

tk.Button(root, text="旅游攻略", command=travel, font=("Arial", 20, "bold"), bg="lightblue", fg="blue", width=10).grid(
    row=3, column=1, padx=10, pady=10
)

root.mainloop()

10. 运行方法

按照下面的步骤运行项目:

  1. 安装依赖:
pip install openai tavily-python requests
  1. 设置环境变量,需要替换成你自己的真实密钥:
export DEEPSEEK_API_KEY="你的DeepSeek密钥"
export TAVILY_API_KEY="你的Tavily密钥"

Windows 用户可以使用 $env:KEY="value"set KEY=value 的方式设置。

  1. 运行 Python 脚本:
python chatbot.py

启动后即可在主窗口聊天,或分别打开天气查询、旅游攻略窗口体验。

11. 小结

这个项目虽然不大,但已经把桌面 GUI、AI 对话、实时网络请求和第三方搜索服务整合到了一起。对于学习 Python 综合开发来说,是一个很实用的练手案例。

更重要的是,我们借此完成了一次安全改造:不再在源码中硬编码密钥,而是改用环境变量管理。以后如果要把代码分享到博客、GitHub 或教学文章里,就不会意外泄露自己的 API 密钥了。

你可以继续扩展这个项目,例如:

  • 增加历史记录保存;
  • 使用多线程避免界面卡顿;
  • 增加更多服务接口,比如新闻、翻译、日程提醒;
  • 把界面换成更现代的 ttkbootstrap 或 PyQt。

多线程改造:避免界面卡顿

当前代码中,send_messageget_weatherget_attraction 三个函数都是同步调用 API,在等待网络响应期间,Tkinter 主线程会被阻塞,导致窗口无响应(表现为“卡死”或“白屏”)。解决办法是把耗时的 API 调用放到子线程中执行,主线程只负责更新界面。

核心思路是:子线程负责网络请求,主线程通过 root.after() 调度界面更新。因为 Tkinter 不是线程安全的,子线程不能直接操作控件,必须把结果交回主线程处理。

send_message 为例,改造后的代码如下:

import threading

def send_message():
    query = entry.get().strip()
    if not query:
        text.insert(tk.END, "系统:请输入您的问题!\n")
        return

    text.insert(tk.END, "你:" + query + "\n")
    entry.delete(0, tk.END)
    btn1.config(state=tk.DISABLED, text="正在思考中...")

    if not DEEPSEEK_API_KEY:
        text.insert(tk.END, "系统:未配置 DEEPSEEK_API_KEY 环境变量。\n")
        btn1.config(state=tk.NORMAL, text="发送")
        return

    def worker():
        """子线程:执行耗时的 API 调用"""
        try:
            client = OpenAI(
                api_key=DEEPSEEK_API_KEY,
                base_url="https://api.deepseek.com",
                timeout=30.0,
            )
            response = client.chat.completions.create(
                model="deepseek-v4-pro",
                messages=[
                    {"role": "system", "content": "You are a helpful assistant"},
                    {"role": "user", "content": query},
                ],
                stream=False,
                reasoning_effort="high",
                extra_body={"thinking": {"type": "enabled"}},
            )
            ans = response.choices[0].message.content
            # 通过 after 把结果交回主线程更新界面
            root.after(0, lambda: text.insert(tk.END, "机器人:" + ans + "\n"))
        except openai.APIConnectionError as e:
            root.after(0, lambda: text.insert(tk.END, f"机器人:网络连接失败,请检查网络后重试。\n详细信息:{e}\n"))
        except openai.AuthenticationError as e:
            root.after(0, lambda: text.insert(tk.END, f"机器人:API 密钥无效或已过期,请检查 DEEPSEEK_API_KEY 配置。\n详细信息:{e}\n"))
        except openai.RateLimitError as e:
            root.after(0, lambda: text.insert(tk.END, f"机器人:请求过于频繁或账户余额不足,请稍后再试。\n详细信息:{e}\n"))
        except openai.APIStatusError as e:
            root.after(0, lambda: text.insert(tk.END, f"机器人:DeepSeek 服务返回错误(状态码 {e.status_code})。\n详细信息:{e}\n"))
        except Exception as e:
            root.after(0, lambda: text.insert(tk.END, f"机器人:发生未知错误 - {e}\n"))
        finally:
            # 恢复按钮状态也要回到主线程
            root.after(0, lambda: btn1.config(state=tk.NORMAL, text="发送"))

    # 启动子线程,主线程立即返回,界面保持响应
    threading.Thread(target=worker, daemon=True).start()

关键点说明:

  • threading.Thread(target=worker, daemon=True).start():启动一个守护线程执行 API 调用,主线程立即返回,界面不会卡住;
  • root.after(0, lambda: ...):把界面更新操作调度回主线程执行,避免跨线程操作 Tkinter 控件;
  • daemon=True:设为守护线程,程序退出时子线程会自动结束,不会阻塞关闭;
  • 按钮状态恢复finally 块中的按钮恢复操作同样通过 root.after() 回到主线程执行。

get_weatherget_attraction 的改造方式完全一致,只需把 requests.get()tavily.search() 的调用放进 worker 子线程,并把所有 text2.insert / text3.insert 以及按钮状态恢复都包进 root.after(0, lambda: ...) 即可。改造后,即使网络请求耗时较长,窗口也能保持流畅响应,用户可以继续操作或随时关闭窗口。
希望这篇文章对你有所帮助。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐