用 ChatterBot 和 Ollama 在本地搭一个属于你的 Python 聊天机器人

2026-05-06 22 预计阅读时间:1 分钟
来源:realpython.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:9 分钟

开源聊天机器人框架 ChatterBot 最近重新活跃起来,配合本地大模型 Ollama,可以在完全离线的环境下跑起一个有记忆、可定制、还能调用本地推理能力的对话系统。下面从数据清洗、模型训练到接入 Ollama,走一遍完整流程。

ChatterBot 的核心机制

ChatterBot 不是端到端的大模型,它是一个基于检索的对话引擎:把每条输入和已有语料做相似度匹配,选出最佳回复。好处是训练快、资源占用低,坏处是回复上限取决于语料质量和覆盖面。

它的工作链路:

用户输入 → 逻辑适配器(Logic Adapter) → 语料存储(Statement Storage) → 返回最佳匹配回复

默认逻辑适配器用 BestMatch,底层相似度算法可选 TF-IDF 或 Jaccard。存储后端默认是 JSON 文件,也可以换成 Django ORM 或 SQLAlchemy。

清洗真实对话数据

真实对话数据往往充满噪音:表情符号、重复句、拼写错误、上下文断裂。ChatterBot 的训练器只接受"输入-回复"对,所以清洗的核心是把这些对干净地提取出来。

假设你有一份聊天记录 CSV,结构是 timestamp, user, message。清洗思路:

  1. 按时间排序,相邻消息构成对话对。
  2. 过滤掉纯表情、系统提示、过短消息。
  3. 去重——同一输入对应多个不同回复时,保留最长的那个。
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 生成质量取决于模型大小和硬件。对于内部工具、客服原型、知识问答这类场景,这个组合已经够用——先跑起来,再根据实际对话数据迭代。


相关推荐