ChatGPT API实战:如何高效构建知识库问答系统

1次阅读
没有评论

共计 4082 个字符,预计需要花费 11 分钟才能阅读完成。

image.webp

引言

在实际应用中,ChatGPT API 虽然在生成自然语言回复方面表现出色,但当涉及到需要从特定知识库中检索信息时,直接使用 API 调用往往效果不佳。本文将带你一步步构建一个基于知识库的问答系统,解决长上下文管理和知识检索精度的问题。

ChatGPT API 实战:如何高效构建知识库问答系统

1. 核心痛点分析

  1. 上下文长度限制 :ChatGPT API 对每次请求的 token 数量有限制(目前 GPT-3.5-turbo 是 4096 tokens),当知识库内容较多时,无法全部放入上下文。
  2. 检索精度问题 :直接使用 API 进行问答时,模型可能会 ” 编造 ” 答案(hallucination),特别是当问题涉及专业知识时。
  3. 成本控制 :将大量知识库内容直接放入 prompt 会导致高昂的 API 调用成本。

2. 架构设计

2.1 直接 API 调用的问题

  • 需要把所有相关知识放入 prompt
  • 上下文 token 快速消耗
  • 检索效率低下

2.2 知识库预处理方案

  1. 分层处理架构 :
  2. 知识库预处理层:将知识库内容向量化并建立索引
  3. 检索层:根据用户问题检索最相关的知识片段
  4. 生成层:将检索结果和问题一起发送给 ChatGPT 生成回答

  5. 优势对比 :

  6. 减少每次 API 调用的 token 使用量
  7. 提高回答的准确性
  8. 降低 API 调用成本

3. 代码实现

3.1 环境准备

import openai
from typing import List, Dict
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
import pickle
import os

3.2 知识库向量化

class KnowledgeBase:
    def __init__(self, openai_key):
        self.openai_key = openai_key
        openai.api_key = openai_key
        self.knowledge_embeddings = {}

    def add_document(self, document_id: str, text: str):
        """将文档文本转化为 embedding 并存储"""
        response = openai.Embedding.create(
            input=text,
            model="text-embedding-ada-002"
        )
        self.knowledge_embeddings[document_id] = {
            'text': text,
            'embedding': response['data'][0]['embedding']
        }

    def save(self, filepath):
        """保存知识库到文件"""
        with open(filepath, 'wb') as f:
            pickle.dump(self.knowledge_embeddings, f)

    def load(self, filepath):
        """从文件加载知识库"""
        with open(filepath, 'rb') as f:
            self.knowledge_embeddings = pickle.load(f)

3.3 对话上下文管理

class ConversationManager:
    def __init__(self, max_context_length=3000):
        self.max_context_length = max_context_length
        self.context = []

    def add_message(self, role: str, content: str):
        """添加对话消息到上下文"""
        self.context.append({"role": role, "content": content})

    def get_relevant_context(self, question_embedding, knowledge_base, top_n=3):
        """根据问题检索最相关的知识片段"""
        similarities = {}
        for doc_id, doc_data in knowledge_base.knowledge_embeddings.items():
            sim = cosine_similarity([question_embedding],
                [doc_data['embedding']]
            )[0][0]
            similarities[doc_id] = sim

        sorted_docs = sorted(similarities.items(), key=lambda x: x[1], reverse=True)[:top_n]
        return [knowledge_base.knowledge_embeddings[doc_id]['text'] for doc_id, _ in sorted_docs]

    def prepare_prompt(self, question, relevant_knowledge):
        """准备发送给 ChatGPT 的 prompt"""
        knowledge_str = '\n'.join([f"知识片段 {i+1}: {text}" for i, text in enumerate(relevant_knowledge)])
        prompt = f""" 根据以下知识回答问题:
{knowledge_str}

问题: {question}
答案:"""

        # 确保不超过 token 限制
        estimated_tokens = len(prompt.split()) * 1.3  # 粗略估计
        if estimated_tokens > self.max_context_length:
            # 简化知识片段
            knowledge_str = '\n'.join([text[:500] for text in relevant_knowledge])
            prompt = f""" 根据以下知识回答问题:
{knowledge_str}

问题: {question}
答案:"""

        return prompt

3.4 API 调用封装

class ChatGPTWrapper:
    def __init__(self, openai_key, model="gpt-3.5-turbo"):
        self.openai_key = openai_key
        self.model = model
        openai.api_key = openai_key

    def generate_response(self, prompt, max_retries=3):
        """调用 ChatGPT API 生成回答,包含重试逻辑"""
        for attempt in range(max_retries):
            try:
                response = openai.ChatCompletion.create(
                    model=self.model,
                    messages=[{"role": "user", "content": prompt}],
                    temperature=0.7
                )
                return response.choices[0].message.content
            except Exception as e:
                if attempt == max_retries - 1:
                    raise
                time.sleep(2 ** attempt)  # 指数退避 

4. 性能优化

  1. 缓存策略 :
  2. 缓存常见问题的答案
  3. 缓存文档的 embeddings,避免重复计算

  4. 批量处理 embeddings:

  5. 当需要处理大量文档时,使用 OpenAI 的批量 embedding 接口
  6. 示例代码:

    def batch_add_documents(self, documents: Dict[str, str], batch_size=20):
        """批量添加文档"""
        doc_ids = list(documents.keys())
        for i in range(0, len(doc_ids), batch_size):
            batch_ids = doc_ids[i:i+batch_size]
            batch_texts = [documents[doc_id] for doc_id in batch_ids]
    
            response = openai.Embedding.create(
                input=batch_texts,
                model="text-embedding-ada-002"
            )
    
            for j, doc_id in enumerate(batch_ids):
                self.knowledge_embeddings[doc_id] = {'text': documents[doc_id],
                    'embedding': response['data'][j]['embedding']
                }

  7. 异步处理 :

  8. 对于大规模知识库,使用异步方式处理 embedding 生成

5. 避坑指南

  1. 知识库版本一致性 :
  2. 当知识库更新时,确保所有服务节点同步更新
  3. 可以使用版本号或时间戳标记知识库版本

  4. 上下文 token 超限 :

  5. 实时监控 token 使用量
  6. 当接近限制时,优先保留最相关的上下文
  7. 示例检查函数:

    def estimate_token_usage(self, text):
        """粗略估计文本的 token 数量"""
        return len(text.split()) * 1.3  # 英语大约 1.3 tokens/word

  8. 敏感信息过滤 :

  9. 在将用户问题和知识库内容发送给 API 前,进行敏感词过滤
  10. 可以使用正则表达式或专业过滤库
  11. 示例代码:
    SENSITIVE_WORDS = [...]  # 定义敏感词列表
    
    def filter_sensitive_content(self, text):
        for word in SENSITIVE_WORDS:
            text = text.replace(word, '[REDACTED]')
        return text

6. 实践建议

  1. 知识库预处理 :
  2. 将知识库分割为适当大小的片段(通常 200-500 字)
  3. 为每个片段添加有意义的元数据(如来源、创建时间等)

  4. 测试与评估 :

  5. 构建测试集验证系统准确性
  6. 监控 API 调用成本和响应时间

  7. 渐进式增强 :

  8. 从少量核心知识开始,逐步扩展
  9. 根据用户反馈优化知识库结构

结语

通过本文介绍的方法,你可以构建一个高效的知识库问答系统,解决直接使用 ChatGPT API 的诸多限制。这种分层架构不仅提高了回答的准确性,还能有效控制 API 调用成本。

开放性问题 :当知识库规模达到千万级时,如何优化检索效率?这涉及到分布式索引、近似最近邻搜索等高级技术,值得深入探讨。

正文完
 0
评论(没有评论)