开源聊天机器人框架 ChatterBot 最近重新活跃起来,配合本地大模型 Ollama,可以在完全离线的环境下跑起一个有记忆、可定制、还能调用本地推理能力的对话系统。下面从数据清洗、模型训练到接入 Ollama,走一遍完整流程。
ChatterBot 的核心机制
ChatterBot 不是端到端的大模型,它是一个基于检索的对话引擎:把每条输入和已有语料做相似度匹配,选出最佳回复。好处是训练快、资源占用低,坏处是回复上限取决于语料质量和覆盖面。
它的工作链路:
用户输入 → 逻辑适配器(Logic Adapter) → 语料存储(Statement Storage) → 返回最佳匹配回复
默认逻辑适配器用 BestMatch,底层相似度算法可选 TF-IDF 或 Jaccard。存储后端默认是 JSON 文件,也可以换成 Django ORM 或 SQLAlchemy。
清洗真实对话数据
真实对话数据往往充满噪音:表情符号、重复句、拼写错误、上下文断裂。ChatterBot 的训练器只接受"输入-回复"对,所以清洗的核心是把这些对干净地提取出来。
假设你有一份聊天记录 CSV,结构是 timestamp, user, message。清洗思路:
- 按时间排序,相邻消息构成对话对。
- 过滤掉纯表情、系统提示、过短消息。
- 去重——同一输入对应多个不同回复时,保留最长的那个。
import pandas as pd
import re
def clean_message(text: str) -> str:
"""去掉表情符号和多余空白,保留核心文本"""
text = re.sub(r'[^\w\s,。!?、;:""()]', '', text)
text = re.sub(r'\s+', ' ', text).strip()
return text
def build_pairs(df: pd.DataFrame) -> list[tuple[str, str]]:
"""从聊天记录中提取 (输入, 回复) 对"""
df = df.sort_values('timestamp')
messages = df['message'].tolist()
users = df['user'].tolist()
pairs = []
for i in range(len(messages) - 1):
if users[i] != users[i + 1]: # 不同用户之间的相邻消息
inp = clean_message(messages[i])
resp = clean_message(messages[i + 1])
if len(inp) >= 3 and len(resp) >= 3: # 过滤过短消息
pairs.append((inp, resp))
# 去重:同一输入保留最长回复
deduped = {}
for inp, resp in pairs:
if inp not in deduped or len(resp) > len(deduped[inp]):
deduped[inp] = resp
return list(deduped.items())
# 使用示例
df = pd.read_csv('chat_logs.csv')
pairs = build_pairs(df)
print(f"清洗后得到 {len(pairs)} 个对话对")
这段代码可以直接改造——如果你的数据源是 JSON 或数据库导出,只需调整 pd.read_csv 部分即可。
用自定义语料训练 ChatterBot
清洗完数据后,用 ListTrainer 把对话对喂给机器人:
from chatterbot import ChatBot
from chatterbot.trainers import ListTrainer
# 创建机器人,指定中文语料存储
chatbot = ChatBot(
'MyBot',
storage_adapter='chatterbot.storage.SQLStorageAdapter',
logic_adapters=[
{
'import_path': 'chatterbot.logic.BestMatch',
'default_response': '我暂时不太明白你的意思,换个说法试试?',
'maximum_similarity_threshold': 0.90 # 低于此阈值返回默认回复
}
],
database_uri='sqlite:///chatbot_db.sqlite3'
)
trainer = ListTrainer(chatbot)
# pairs 是上一节清洗出来的 (输入, 回复) 列表
for statement, response in pairs:
trainer.train([statement, response])
# 快速测试
print(chatbot.get_response('你好'))
几个关键参数说明:
maximum_similarity_threshold:设太高会让机器人频繁返回默认回复,设太低会匹配到不相关内容。中文语料建议从 0.85 开始调试。SQLStorageAdapter:比默认 JSON 存储更稳定,支持并发读写。- 训练数据量在几百到几千对时,ChatterBot 就能覆盖常见问答场景;再多的话,检索延迟会上升,这时可以考虑换后端或引入 Ollama。
接入 Ollama:让本地大模型兜底
ChatterBot 的检索能力有上限——遇到语料里没有的问题就只能返回默认回复。一个务实的做法是:ChatterBot 先尝试匹配,匹配失败时把问题转发给 Ollama 本地模型,拿到生成式回复再返回。
先确保 Ollama 已经在本地运行:
# 安装 Ollama(macOS / Linux)
curl -fsSL https://ollama.com/install.sh | sh
# 拉一个轻量中文模型
ollama pull qwen2:1.5b
# 验证服务可用
curl http://localhost:11434/api/tags
然后在 Python 中写一个混合适配器:
import requests
from chatterbot.logic import LogicAdapter
from chatterbot.conversation import Statement
class OllamaFallbackAdapter(LogicAdapter):
"""ChatterBot 匹配失败时,调用 Ollama 生成回复"""
def __init__(self, chatbot, **kwargs):
super().__init__(chatbot, **kwargs)
self.ollama_url = kwargs.get('ollama_url', 'http://localhost:11434/api/generate')
self.model = kwargs.get('ollama_model', 'qwen2:1.5b')
self.confidence_threshold = kwargs.get('confidence_threshold', 0.5)
def process(self, input_statement: Statement, additional_response_selection_parameters=None):
# 先用 BestMatch 的逻辑找最佳匹配
best_match = self.chatbot.storage.search(
input_statement.text,
maximum_similarity_threshold=self.confidence_threshold
)
if best_match and best_match.confidence >= self.confidence_threshold:
return best_match, best_match.confidence
# 匹配失败,调用 Ollama
prompt = input_statement.text
payload = {
'model': self.model,
'prompt': prompt,
'stream': False
}
try:
resp = requests.post(self.ollama_url, json=payload, timeout=30)
resp.raise_for_status()
generated = resp.json().get('response', '').strip()
except Exception as e:
generated = f'本地模型暂时无法响应:{e}'
response_statement = Statement(text=generated)
response_statement.confidence = 0.4 # 生成式回复置信度标记低一些
return response_statement, response_statement.confidence
把自定义适配器注册到 ChatBot:
chatbot = ChatBot(
'HybridBot',
storage_adapter='chatterbot.storage.SQLStorageAdapter',
logic_adapters=[
{
'import_path': 'OllamaFallbackAdapter',
'ollama_url': 'http://localhost:11434/api/generate',
'ollama_model': 'qwen2:1.5b',
'confidence_threshold': 0.5
}
],
database_uri='sqlite:///chatbot_db.sqlite3'
)
# 测试:语料里有的问题走检索,没有的走 Ollama
print(chatbot.get_response('你好')) # 走检索
print(chatbot.get_response('解释一下量子计算')) # 走 Ollama
注意:OllamaFallbackAdapter 类定义需要放在能被 import_path 找到的模块里。最简单的方式是把它写在一个单独文件 ollama_adapter.py 中,然后配置 'import_path': 'ollama_adapter.OllamaFallbackAdapter'。
部署和调优清单
把这套系统跑起来之后,有几个值得关注的点:
| 关注点 | 建议 |
|---|---|
| 语料质量 | 定期从真实对话中增量提取新对,重新训练;脏数据比没有数据更糟 |
| 匹配阈值 | 用 20-50 条测试对话调 maximum_similarity_threshold,找到误匹配和漏匹配的平衡点 |
| Ollama 模型选择 | 1.5b 参数量适合兜底短回复;如果需要更长、更准确的生成,换 qwen2:7b,但推理延迟会从 1-2 秒升到 5-10 秒 |
| 并发安全 | SQLite 后端在多线程下需要加锁;生产环境建议换 PostgreSQL 或 Django ORM 后端 |
| 回复缓存 | Ollama 对同一 prompt 的回复可以缓存到 ChatterBot 语料库,下次同类问题直接检索命中,省掉推理开销 |
最后一点尤其值得做:每次 Ollama 生成的优质回复,自动回写进 ChatterBot 的语料存储。这样系统会越用越聪明,Ollama 的调用频率也会逐步下降。
整套方案的优势是完全本地化、不依赖外部 API、数据不出机器。代价是检索式回复的灵活度有限,Ollama 生成质量取决于模型大小和硬件。对于内部工具、客服原型、知识问答这类场景,这个组合已经够用——先跑起来,再根据实际对话数据迭代。