BounceChat全攻略(四):AI对话——让小球真的会聊天
🎬 从“哑巴小球”到“话痨伙伴”
回顾一下前三篇的成果。
第一篇,我们给了小球物理灵魂——它会重力下落,会摩擦减速,会碰撞反弹,像个真正的纸团。
第二篇,我们给了小球一双眼睛——它能看见桌面上所有的窗口,撞到浏览器会弹开,落在文件夹上会停住。
第三篇,我们给了小球灵敏的耳朵——你戳它,它有反应;你扔它,它会滚;你按方向键,它会动。
但还有一个问题:它是个哑巴。
你跟它说“你好”,它没反应;你问它“今天天气怎么样”,它依然自顾自地弹跳。这哪里是伙伴,分明是个自闭的玩具。
一个真正的桌面小伙伴,应该是你问它答,你聊它应。
那么问题来了:怎么让小球学会说话?怎么让它的回答像“小球”而不是冷冰冰的机器?
这就是本篇要解决的问题。

📦 拆解需求:什么叫“会聊天”?
同样,我们先拆解一下“会聊天”这个模糊目标:
| 🎯 理想场景 | ⚙️ 技术需求 |
|---|---|
| 你打字问“你好”,它回答“你好呀” | 接收输入 + 生成回复 |
| 它记得你刚才问过什么 | 对话历史管理 |
| 它的回答不超过20字,像个小球 | 角色设定 + 长度限制 |
| 关掉程序再打开,它还记得昨天聊过 | 持久化存储 |
| API 可能失败,它得有礼貌地道歉 | 错误处理 |
拆开之后发现,原来“会聊天”是由一连串子任务组成的。
💡 思路:大模型 + 对话管理
让程序“说话”有几种做法:
| 做法 | 原理 | 优缺点 |
|---|---|---|
| 规则匹配 | if “你好” then “你好呀” | ❌ 太死板,聊不了三句 |
| 预定义语料库 | 随机返回预设句子 | ❌ 聊久了重复,像个复读机 |
| 大模型(本方案) | 调用 AI 接口生成回复 | ✅ 灵活、智能、像真人 ❌ 需要联网、有费用 |
显然,想让小球真的“会聊天”,得用大模型。
那选哪个模型?BounceChat 接的是模力方舟的 DeepSeek-V3,原因很简单:
- 每日免费 100 次——够你玩很久
- 响应快——不像某些模型要等半天
- 支持角色设定——可以让它扮演“弹跳小球”

🛠️ 该怎么做:一步步让小球开口说话
📁 第一步:先建个“大脑”模块
在 chat.py 里,我们定义了一个 ChatModel 类,专门处理所有和 AI 相关的事情。
from openai import OpenAI
import json
import os
class ChatModel:
def __init__(self):
"""初始化大模型客户端"""
self.client = OpenAI(
base_url="https://ai.gitee.com/v1",
api_key="你的API密钥", # 记得改成自己的!
default_headers={"X-Failover-Enabled": "true"},
)
# 聊天历史文件路径
self.chat_log_file = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"chat_history.json"
)
# 加载聊天历史
self.chat_history = self.load_chat_history()
🗣️ 为什么单独建一个类?
这叫模块化设计——把所有和 AI 相关的代码集中到一起,主程序 (
main.py) 只管调用。以后想换模型、改参数,都在一个地方改,不用满世界找代码。
🤖 第二步:给小球一个“人设”
要让小球回答得像小球,而不是像百科全书,需要给它设定一个角色。
def load_chat_history(self):
"""加载聊天历史"""
try:
if os.path.exists(self.chat_log_file):
with open(self.chat_log_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"加载聊天历史失败: {e}")
# 默认系统消息:给小球设定角色
return [
{
"role": "system",
"content": "你是一个运行在用户电脑桌面上的弹跳小球,你回答内容的长度不能超过20字"
}
]
这个 system 消息非常关键。它告诉大模型:
- 你是谁:弹跳小球
- 你怎么回答问题:长度 ≤20 字
💡 为什么是 20 字?
试出来的。太长像念课文,太短像敷衍。20 字刚好是“一句完整但简洁的话”的长度。
看几个真实回复的例子:
- “你好,我是弹跳小球。”
- “今天很开心!”
- “我是小球,不看天气哦。”
是不是有内味儿了?
💬 第三步:发送消息并获取回复
def send_message(self, message):
"""发送消息给大模型并获取回复"""
# 添加用户消息到聊天历史
self.chat_history.append({
"role": "user",
"content": message
})
try:
response = self.client.chat.completions.create(
messages=self.chat_history,
model="DeepSeek-V3_1",
extra_body={
"thinking": {"type": "disabled"},
"top_k": 20,
},
stream=False,
max_tokens=1024,
temperature=0.6,
top_p=0.8,
frequency_penalty=1.1,
)
# 获取回复内容
if len(response.choices) > 0:
assistant_reply = response.choices[0].message.content
# 添加助手回复到聊天历史
self.chat_history.append({
"role": "assistant",
"content": assistant_reply
})
# 保存聊天历史
self.save_chat_history()
return assistant_reply
else:
return "抱歉,我没有理解你的问题,请重试。"
except Exception as e:
print(f"大模型调用失败: {e}")
return "抱歉,我暂时无法回答你的问题,请稍后再试。"
这里面有几个重要参数:
| 参数 | 值 | 作用 |
|---|---|---|
temperature |
0.6 | 控制随机性。越低越稳定,越高越天马行空 |
max_tokens |
1024 | 最大回复长度(这里其实用不上,因为系统消息已经限制了) |
frequency_penalty |
1.1 | 避免重复说一样的话 |
top_k |
20 | 每次从概率最高的 20 个词里选,保证不跑偏 |
🎯 调参心得:
0.6 的 temperature 是“既稳定又有趣”的平衡点。太低像机器人,太高会胡言乱语。
💾 第四步:让小球有“记忆力”
关掉程序再打开,小球应该记得之前聊过什么。这就需要持久化存储。
def save_chat_history(self):
"""保存聊天历史"""
try:
with open(self.chat_log_file, 'w', encoding='utf-8') as f:
json.dump(self.chat_history, f, ensure_ascii=False, indent=2)
except Exception as e:
print(f"保存聊天历史失败: {e}")
每次聊天后,把整个 chat_history 存成 JSON 文件。下次启动时,load_chat_history 读回来。
📁 chat_history.json 长啥样?
[ { "role": "system", "content": "你是一个运行在用户电脑桌面上的弹跳小球,你回答内容的长度不能超过20字" }, { "role": "user", "content": "你好" }, { "role": "assistant", "content": "你好,我是弹跳小球。" } ]
这样,每次调用 API 时,把整个历史传进去,大模型就知道上下文了。
🔗 第五步:把大脑接到身体上
现在 ChatModel 写好了,怎么把它和 main.py 里的小球连接起来?
在 BallWidget 的 __init__ 里初始化:
from chat import ChatModel
class BallWidget(QWidget):
def __init__(self):
# ... 前面的代码 ...
# 初始化大模型客户端
self.chat_model = ChatModel()
然后在用户发送消息时调用:
def on_message_sent(self, message):
"""处理从输入框发送的消息"""
print(f"收到消息: {message}")
# 发送消息给大模型
assistant_reply = self.chat_model.send_message(message)
print(f"大模型回复: {assistant_reply}")
# 更新气泡内容显示回复
self.update_bubble_content(assistant_reply)
这里 on_message_sent 是由输入框的 WebChannel 回调触发的,我们会在下一篇详细讲。
🧪 第六步:测试对话
写个简单的测试代码(就在 chat.py 末尾):
if __name__ == "__main__":
chat_model = ChatModel()
response = chat_model.send_message("你好,你是?")
print(f"回复: {response}")
response2 = chat_model.send_message("今天天气怎么样?")
print(f"回复: {response2}")
运行看看输出:
回复: 你好,我是弹跳小球。
回复: 我是小球,不看天气哦!
成了!小球真的会说话了。

🧠 深入思考:对话管理的几个坑
坑一:历史太长怎么办?
每次调用都把整个历史传进去,聊久了历史会越来越长,消耗 tokens 也变多。
解法:可以设置最大历史长度,比如只保留最近 10 轮对话。
MAX_HISTORY = 20 # 保留 20 条消息(10轮)
if len(self.chat_history) > MAX_HISTORY + 1: # +1 是为了保留 system 消息
# 保留 system 消息 + 最近 MAX_HISTORY 条
self.chat_history = [self.chat_history[0]] + self.chat_history[-MAX_HISTORY:]
坑二:角色设定被冲掉怎么办?
有时候聊着聊着,小球就忘了自己是小球,开始像普通 AI 一样回答问题。
解法:每次调用 API 前,检查 system 消息还在不在,不在就插回去。
if self.chat_history[0]["role"] != "system":
self.chat_history.insert(0, {
"role": "system",
"content": "你是一个运行在用户电脑桌面上的弹跳小球,你回答内容的长度不能超过20字"
})
坑三:API 限流或失败怎么办?
免费 API 有调用次数限制,或者网络可能出问题。
解法:错误处理 + 本地兜底回复。
try:
response = self.client.chat.completions.create(...)
except Exception as e:
print(f"大模型调用失败: {e}")
return "抱歉,我现在有点卡顿,等会儿再聊好吗?"
🤔 思考:有没有其他做法?
当然有。对话接入不止这一种方案。
| 方法 | 原理 | 优缺点 |
|---|---|---|
| OpenAI SDK(本方案) | 官方 Python 库调用 | ✅ 简单、标准 ✅ 支持流式输出 ❌ 依赖网络 |
| HTTP 直接请求 | 用 requests 调 API |
✅ 更底层、可控 ❌ 要自己处理认证、重试 |
| 本地模型 | 用 llama.cpp 跑本地模型 |
✅ 免费、离线 ❌ 吃配置、慢 |
| 预定义回复 | 随机返回预设句子 | ✅ 极快、无网络 ❌ 聊不了几句就重复 |
🏆 为什么选 OpenAI SDK?
因为简单。几行代码就能接入,而且模力方舟的接口和 OpenAI 兼容,直接用 SDK 省去很多麻烦。
📚 总结:从这个小球,我们又学到了什么?
🎯 一、模块化设计
把 AI 对话相关的代码单独放到 chat.py,主程序只负责调用。这叫关注点分离——每个文件只做一件事。
🔄 二、角色设定
通过 system 消息给 AI 设定“人设”,让它知道自己是“弹跳小球”,而不是万能的百科全书。
💾 三、持久化存储
用 JSON 文件保存对话历史,让小球有“记忆力”,关掉程序再打开还能记得你是谁。
🧪 四、错误处理
API 调用可能失败,要有兜底方案,给用户友好的提示而不是直接崩溃。
⚖️ 五、参数调优
temperature、max_tokens、frequency_penalty 这些参数不是摆设,调好了能让回复更自然。
🧰 可迁移的思维工具
这套“AI 对话”的方法论,可以迁移到很多场景:
| 场景 | 要做什么 | 关键点 |
|---|---|---|
| 客服机器人 | 自动回复用户问题 | 角色设定、知识库 |
| 翻译工具 | 翻译文本 | 提示词工程 |
| 代码助手 | 生成代码 | 上下文管理 |
| 角色扮演 | 扮演特定人物 | 系统消息设定 |
💎 核心心法:无论接什么 AI,流程都是:
- 选择模型和 API
- 设计角色设定
- 管理对话历史
- 处理错误和限流
- 持久化存储
📝 本篇要点回顾
| 模块 | 核心内容 |
|---|---|
| 模型选择 | 模力方舟 DeepSeek-V3(每日免费 100 次) |
| 角色设定 | system 消息:“你是弹跳小球,回答 ≤20 字” |
| 对话管理 | 保存历史到 JSON,下次启动时加载 |
| 参数调优 | temperature=0.6, frequency_penalty=1.1 |
| 错误处理 | try-catch 捕获异常,返回友好提示 |
| 模块化 | 单独 ChatModel 类,主程序只调用 |
更多推荐




所有评论(0)