📍 你在这里 AI 应用 · 第 5 篇 / 共 8 篇

← 上一篇 AI 应用开发新范式:Agent 与 MCP · 下一篇 → LangGraph 入门:与流程图的本质区别

LangChain 是一个用于构建基于大型语言模型(LLM)的应用程序的开源框架,它通过提供一系列工具、组件和接口,简化了将 LLM 与外部数据、其他系统进行集成的过程,帮助开发者更高效地构建复杂的 AI 应用。以下从核心概念、主要功能、应用场景等方面进行详细介绍:

一、核心定位与目标

LangChain 的核心目标是解决 LLM 应用开发中的关键挑战:

  • LLM 本身的知识局限(基于训练数据,无法实时获取新信息);
  • 与外部数据的交互(如数据库、文件、API 等);
  • 复杂任务的拆解与流程控制(如多步推理、逻辑链构建)。

通过模块化设计,LangChain 让开发者可以像搭积木一样组合不同组件,快速实现从简单问答到复杂智能代理(Agent)的各类应用。

二、核心概念与组件

LangChain 的核心组件围绕“链(Chain)”和“代理(Agent)”展开,主要包括以下模块:

1. 模型(Models)

  • LLM 包装器:支持集成主流 LLM(如 GPT-4、Claude、LLaMA 等),统一调用接口,简化不同模型的切换。
  • 聊天模型(Chat Models):针对对话场景优化,支持基于消息列表(而非纯文本)的交互(如区分“用户消息”“系统提示”)。
  • 文本嵌入模型(Embedding Models):将文本转换为向量(用于相似度计算、检索等),支持 OpenAI Embeddings、Hugging Face 模型等。

2. 提示词(Prompts)

  • 提示词模板(Prompt Templates):标准化提示词格式,支持动态插入变量(如用户输入、上下文信息),避免重复编写固定文本。
    • 示例:"请总结以下内容:{user_input}"
  • 提示词选择器(Prompt Selectors):根据条件(如输入长度、模型类型)自动选择合适的提示词模板。
  • 输出解析器(Output Parsers):将 LLM 的原始输出转换为结构化格式(如 JSON、列表),方便后续处理。

3. 链(Chains)

  • 链是 LangChain 的核心概念,用于将多个组件(如模型、工具、提示词)按逻辑串联,实现复杂功能。
  • 基础链(LLMChain):最简单的链,将提示词模板与 LLM 结合,直接输出结果。
  • 组合链(Combination Chains):
    • SequentialChain:按顺序执行多个链,前一个链的输出作为后一个的输入。
    • RouterChain:根据输入内容动态选择不同的子链执行(类似“路由”)。
  • 工具链(Tool-related Chains):结合外部工具(如搜索引擎、数据库)的链,如 RetrievalQAChain(检索增强问答)。

4. 数据连接(Data Connection)

  • 解决 LLM 与外部数据的交互问题,核心是“检索增强生成(RAG)”流程:
    • 文档加载器(Document Loaders):加载各种来源的文档(如 PDF、Word、网页、数据库等)。
    • 文档分割器(Text Splitters):将长文档拆分为短片段(适配 LLM 上下文窗口)。
    • 向量存储(Vectorstores):存储文档片段的向量表示(如 Pinecone、Chroma、FAISS 等),用于快速相似度检索。
    • 检索器(Retrievers):从向量存储中检索与问题相关的文档片段,作为 LLM 生成答案的参考。

5. 代理(Agents)

  • 代理是具有“决策能力”的组件,能根据用户需求自主选择工具(如搜索、计算、调用 API)并执行多步任务。
  • 工具(Tools):代理可调用的外部功能,如 SerpAPI(搜索引擎)、PythonREPL(执行代码)、SQLDatabaseToolkit(操作数据库)等。
  • 代理类型:
    • ZeroShotAgent:基于单轮提示词决定调用工具。
    • ReActAgent:遵循“思考-行动-观察”循环(Think-Act-Observe),适合复杂推理。
    • StructuredChatAgent:支持结构化输出(如 JSON),适合精确工具调用。

6. 记忆(Memory)

  • 让链或代理具备“记忆”能力,保存对话历史或中间结果,支持多轮交互。
  • 短期记忆:如 ConversationBufferMemory(保存完整对话历史)、ConversationSummaryMemory(保存对话摘要)。
  • 长期记忆:结合外部存储(如数据库)保存长期对话记录。

三、主要功能与优势

  1. 模块化与可扩展性:所有组件均可独立替换(如换模型、换工具),方便适配不同场景。
  2. 简化复杂流程:封装了 RAG、Agent 等复杂逻辑,开发者无需从零实现。
  3. 跨平台兼容性:支持 Python 和 JavaScript/TypeScript 两种主流语言,适配多环境开发。
  4. 丰富的生态集成:已对接数百种工具、模型和存储服务(如 OpenAI、AWS、Google Cloud 等)。

四、典型应用场景

  • 检索增强问答(RAG):结合企业文档、知识库,让 LLM 生成基于特定数据的答案(如客服系统、内部知识库查询)。
  • 智能代理(Agent):自动完成多步任务(如“查天气→订机票→写行程”“分析数据→生成图表→撰写报告”)。
  • 对话机器人:支持多轮对话,具备记忆能力(如客服机器人、聊天助手)。
  • 代码生成与分析:结合代码解释器,实现自动写代码、调试或数据分析。
  • 文档处理:自动总结、翻译、提取信息(如合同分析、论文摘要生成)。

五、学习与使用资源

  • 官方文档:LangChain 文档(含快速入门、API 详解、示例)。
  • 社区生态:活跃的 GitHub 仓库(langchain-ai/langchain)、Discord 社区和教程资源。
  • 入门示例:通过简单代码即可实现 RAG、对话记忆等功能,门槛较低。

总之,LangChain 是 LLM 应用开发的“瑞士军刀”,它降低了构建复杂 AI 系统的难度,让开发者更专注于业务逻辑而非底层集成,是当前构建企业级 LLM 应用的主流框架之一。

import os
import re
from typing import List, Dict, Any, Optional
from langchain.embeddings import OpenAIEmbeddings, HuggingFaceEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.llms import OpenAI, HuggingFaceHub
from langchain.chains import RetrievalQA
from langchain.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
from langchain.prompts import PromptTemplate
from langchain.callbacks import get_openai_callback
import gradio as gr

# 配置类 - 管理所有参数和设置
class Config:
    def __init__(self):
        # 大语言模型配置
        self.llm_provider = "openai"  # 可选: openai, huggingface
        self.openai_api_key = os.environ.get("OPENAI_API_KEY", "")
        self.huggingface_api_token = os.environ.get("HUGGINGFACEHUB_API_TOKEN", "")
        self.llm_model = "gpt-3.5-turbo"  # OpenAI模型
        # self.llm_model = "google/flan-t5-xl"  # HuggingFace模型

        # 文档处理配置
        self.data_directory = "docs"  # 存放文档的目录
        self.embedding_model = "text-embedding-ada-002"  # 嵌入模型
        # self.embedding_model = "sentence-transformers/all-MiniLM-L6-v2"

        # 向量数据库配置
        self.persist_directory = "chroma_db"

        # 链配置
        self.k = 4  # 检索的文档数量
        self.chain_type = "stuff"  # 可选: stuff, map_reduce, refine, map_rerank
        self.return_source_documents = True

        # 界面配置
        self.use_gradio = True  # 是否使用Gradio界面

# 文档处理类 - 加载和处理本地文档
class DocumentProcessor:
    def __init__(self, config: Config):
        self.config = config

    def load_documents(self) -> List[Any]:
        """加载并解析指定目录中的所有文档"""
        print(f"正在从 {self.config.data_directory} 加载文档...")

        # 支持多种文件类型的加载器
        loaders = [
            ("**/*.txt", TextLoader),
            ("**/*.pdf", PyPDFLoader),
            # 可以添加更多文件类型支持
        ]

        all_documents = []
        for pattern, loader_class in loaders:
            try:
                loader = DirectoryLoader(
                    self.config.data_directory,
                    glob=pattern,
                    loader_cls=loader_class
                )
                documents = loader.load()
                all_documents.extend(documents)
                print(f"已加载 {len(documents)} 个 {pattern[3:]} 文件")
            except Exception as e:
                print(f"加载 {pattern[3:]} 文件时出错: {e}")

        return all_documents

    def process_documents(self, documents: List[Any]) -> List[Any]:
        """处理文档:分割文本为较小的块"""
        if not documents:
            print("没有找到文档,请检查文档目录")
            return []

        print(f"正在处理 {len(documents)} 个文档...")
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=200
        )
        split_documents = text_splitter.split_documents(documents)
        print(f"文档已分割为 {len(split_documents)} 个文本块")

        return split_documents

# 向量数据库类 - 管理文档的向量化存储和检索
class VectorDatabase:
    def __init__(self, config: Config):
        self.config = config
        self.embeddings = self._create_embeddings()
        self.db = None

    def _create_embeddings(self) -> Any:
        """创建嵌入模型实例"""
        if self.config.llm_provider == "openai":
            return OpenAIEmbeddings(
                model=self.config.embedding_model,
                openai_api_key=self.config.openai_api_key
            )
        else:
            return HuggingFaceEmbeddings(
                model_name=self.config.embedding_model,
                model_kwargs={"device": "cpu"}  # 使用CPU,如需GPU请改为"cuda"
            )

    def create_or_load_db(self, documents: List[Any]) -> None:
        """创建新的向量数据库或加载已存在的数据库"""
        if documents and len(documents) > 0:
            print("正在创建新的向量数据库...")
            self.db = Chroma.from_documents(
                documents=documents,
                embedding=self.embeddings,
                persist_directory=self.config.persist_directory
            )
            self.db.persist()
            print(f"向量数据库已保存到 {self.config.persist_directory}")
        else:
            print("正在加载已存在的向量数据库...")
            self.db = Chroma(
                embedding_function=self.embeddings,
                persist_directory=self.config.persist_directory
            )
            print("向量数据库加载成功")

    def get_retriever(self) -> Any:
        """获取检索器实例"""
        if self.db is None:
            raise ValueError("向量数据库未初始化")

        return self.db.as_retriever(
            search_kwargs={"k": self.config.k}
        )

# 大语言模型类 - 管理与LLM的交互
class LLMManager:
    def __init__(self, config: Config):
        self.config = config
        self.llm = self._create_llm()

    def _create_llm(self) -> Any:
        """创建大语言模型实例"""
        if self.config.llm_provider == "openai":
            if not self.config.openai_api_key:
                raise ValueError("OpenAI API密钥未设置")

            return OpenAI(
                model_name=self.config.llm_model,
                openai_api_key=self.config.openai_api_key,
                temperature=0.1  # 较低的温度使输出更确定性
            )
        else:
            if not self.config.huggingface_api_token:
                raise ValueError("HuggingFace API令牌未设置")

            return HuggingFaceHub(
                repo_id=self.config.llm_model,
                huggingfacehub_api_token=self.config.huggingface_api_token,
                model_kwargs={"temperature": 0.1, "max_length": 512}
            )

# 对话链类 - 构建完整的对话检索链
class ConversationChain:
    def __init__(self, config: Config, retriever: Any, llm: Any):
        self.config = config
        self.llm = llm
        self.retriever = retriever
        self.memory = ConversationBufferMemory(
            memory_key="chat_history",
            return_messages=True
        )
        self.chain = self._create_chain()

    def _create_chain(self) -> Any:
        """创建对话检索链"""
        # 自定义提示模板,使回答更符合需求
        template = """
        你是一个智能助手,帮助用户解答问题。请根据以下已知信息回答问题。
        如果已知信息不足,请直接说你不知道,不要编造信息。
        对话历史: {chat_history}
        用户问题: {question}
        已知信息: {context}
        回答:
        """

        QA_CHAIN_PROMPT = PromptTemplate(
            input_variables=["chat_history", "question", "context"],
            template=template,
        )

        # 创建对话检索链
        return ConversationalRetrievalChain.from_llm(
            self.llm,
            retriever=self.retriever,
            memory=self.memory,
            chain_type=self.config.chain_type,
            return_source_documents=self.config.return_source_documents,
            combine_docs_chain_kwargs={"prompt": QA_CHAIN_PROMPT}
        )

    def ask(self, question: str) -> Dict[str, Any]:
        """向链提问并获取回答"""
        with get_openai_callback() as cb:  # 仅在使用OpenAI时有效
            result = self.chain({"question": question})

        # 处理回答,提取来源
        answer = result["answer"].strip()
        source_documents = result.get("source_documents", [])
        sources = []

        for doc in source_documents:
            source = os.path.basename(doc.metadata.get("source", ""))
            page = doc.metadata.get("page", "N/A")
            sources.append(f"{source} (第{page}页)" if page != "N/A" else source)

        # 整理回答
        response = {
            "answer": answer,
            "sources": sources if sources else ["无"],
            "tokens_used": getattr(cb, "total_tokens", 0) if self.config.llm_provider == "openai" else 0
        }

        return response

# 界面类 - 提供用户交互界面
class ChatInterface:
    def __init__(self, conversation_chain: ConversationChain, config: Config):
        self.conversation_chain = conversation_chain
        self.config = config

    def run_cli(self) -> None:
        """运行命令行界面"""
        print("\n===== 智能对话机器人已启动 =====")
        print("输入问题开始对话,输入'退出'结束对话")

        while True:
            user_input = input("\n你: ")
            if user_input.lower() in ["退出", "q", "quit", "exit"]:
                break

            if not user_input.strip():
                continue

            response = self.conversation_chain.ask(user_input)

            print(f"\n助手: {response['answer']}")

            if response["sources"] and response["sources"][0] != "无":
                print("\n参考资料:")
                for i, source in enumerate(response["sources"], 1):
                    print(f"  {i}. {source}")

            if response["tokens_used"] > 0:
                print(f"\n本次对话使用了 {response['tokens_used']} 个令牌")

    def run_gradio(self) -> None:
        """运行Gradio Web界面"""
        def chat_interface(user_message, history):
            response = self.conversation_chain.ask(user_message)
            bot_message = response["answer"]

            # 添加参考资料
            if response["sources"] and response["sources"][0] != "无":
                sources_text = "\n\n参考资料:\n" + "\n".join(
                    [f"• {source}" for source in response["sources"]]
                )
                bot_message += sources_text

            history.append((user_message, bot_message))
            return history, ""

        print("\n===== Gradio Web界面已启动 =====")
        print("在浏览器中打开 http://localhost:7860 访问对话界面")

        with gr.Blocks(title="智能对话机器人") as demo:
            chatbot = gr.Chatbot(height=500)
            msg = gr.Textbox(label="输入问题", placeholder="输入问题,按Enter发送,输入'退出'结束对话")
            clear = gr.Button("清空对话")

            msg.submit(chat_interface, [msg, chatbot], [chatbot, msg])
            clear.click(lambda: None, None, chatbot, queue=False)

        demo.launch()

# 主函数 - 整合所有组件
def main():
    # 加载配置
    config = Config()

    try:
        # 初始化文档处理器
        doc_processor = DocumentProcessor(config)
        documents = doc_processor.load_documents()
        processed_docs = doc_processor.process_documents(documents)

        # 初始化向量数据库
        vector_db = VectorDatabase(config)
        vector_db.create_or_load_db(processed_docs)
        retriever = vector_db.get_retriever()

        # 初始化LLM管理器
        llm_manager = LLMManager(config)
        llm = llm_manager.llm

        # 初始化对话链
        conv_chain = ConversationChain(config, retriever, llm)

        # 初始化界面
        chat_interface = ChatInterface(conv_chain, config)

        # 启动界面
        if config.use_gradio:
            chat_interface.run_gradio()
        else:
            chat_interface.run_cli()

    except Exception as e:
        print(f"程序运行出错: {e}")

if __name__ == "__main__":
    main()    
import os
import re
from typing import List, Dict, Any, Optional
from langchain.embeddings import OpenAIEmbeddings, HuggingFaceEmbeddings, QianWenEmbeddings
from langchain.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.llms import OpenAI, HuggingFaceHub, QwenLMEndpoint
from langchain.chains import RetrievalQA
from langchain.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader
from langchain.memory import ConversationBufferWindowMemory
from langchain.chains import ConversationalRetrievalChain
from langchain.prompts import PromptTemplate
from langchain.callbacks import get_openai_callback
import gradio as gr

class Config:
    """配置类 - 管理所有参数和设置"""
    def __init__(self):
        # 大语言模型配置
        self.llm_provider = "qwen"  # 可选: openai, huggingface, qwen
        self.openai_api_key = os.environ.get("OPENAI_API_KEY", "")
        self.huggingface_api_token = os.environ.get("HUGGINGFACEHUB_API_TOKEN", "")
        self.qwen_api_key = os.environ.get("QWEN_API_KEY", "")
        self.llm_model = "qwen-turbo"  # Qwen模型

        # 文档处理配置
        self.data_directory = "docs"  # 存放文档的目录
        self.embedding_model = "text-embedding-ada-002"  # 嵌入模型

        # 向量数据库配置
        self.persist_directory = "chroma_db"

        # 链配置
        self.k = 4  # 检索的文档数量
        self.chain_type = "stuff"  # 可选: stuff, map_reduce, refine, map_rerank
        self.return_source_documents = True

        # 界面配置
        self.use_gradio = True  # 是否使用Gradio界面

        # 记忆配置
        self.memory_window_size = 3  # 保存的对话轮数

class DocumentProcessor:
    """文档处理类 - 加载和处理本地文档"""
    def __init__(self, config: Config):
        self.config = config

    def load_documents(self) -> List[Any]:
        """
        加载并解析指定目录中的所有文档
        支持多种文件类型,包括文本和PDF
        """
        print(f"正在从 {self.config.data_directory} 加载文档...")

        # 定义支持的文件类型及其对应的加载器
        loaders = [
            ("**/*.txt", TextLoader),
            ("**/*.pdf", PyPDFLoader),
            # 可以添加更多文件类型支持
        ]

        all_documents = []
        for pattern, loader_class in loaders:
            try:
                loader = DirectoryLoader(
                    self.config.data_directory,
                    glob=pattern,
                    loader_cls=loader_class
                )
                documents = loader.load()
                all_documents.extend(documents)
                print(f"已加载 {len(documents)} 个 {pattern[3:]} 文件")
            except Exception as e:
                print(f"加载 {pattern[3:]} 文件时出错: {e}")

        return all_documents

    def process_documents(self, documents: List[Any]) -> List[Any]:
        """
        处理文档:将文本分割为较小的块
        便于后续向量化和检索
        """
        if not documents:
            print("没有找到文档,请检查文档目录")
            return []

        print(f"正在处理 {len(documents)} 个文档...")
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,  # 每个文本块的最大长度
            chunk_overlap=200  # 文本块之间的重叠长度,确保上下文连贯
        )
        split_documents = text_splitter.split_documents(documents)
        print(f"文档已分割为 {len(split_documents)} 个文本块")

        return split_documents

class VectorDatabase:
    """向量数据库类 - 管理文档的向量化存储和检索"""
    def __init__(self, config: Config):
        self.config = config
        self.embeddings = self._create_embeddings()
        self.db = None

    def _create_embeddings(self) -> Any:
        """
        创建嵌入模型实例
        根据配置选择不同的嵌入模型
        """
        if self.config.llm_provider == "openai":
            return OpenAIEmbeddings(
                model=self.config.embedding_model,
                openai_api_key=self.config.openai_api_key
            )
        elif self.config.llm_provider == "qwen" and self.config.embedding_model == "qwen-embedding":
            return QianWenEmbeddings(
                qwen_api_key=self.config.qwen_api_key
            )
        else:
            return HuggingFaceEmbeddings(
                model_name=self.config.embedding_model,
                model_kwargs={"device": "cpu"}  # 使用CPU,如需GPU请改为"cuda"
            )

    def create_or_load_db(self, documents: List[Any]) -> None:
        """
        创建新的向量数据库或加载已存在的数据库
        如果提供了文档,则创建新数据库;否则加载现有数据库
        """
        if documents and len(documents) > 0:
            print("正在创建新的向量数据库...")
            self.db = Chroma.from_documents(
                documents=documents,
                embedding=self.embeddings,
                persist_directory=self.config.persist_directory
            )
            self.db.persist()
            print(f"向量数据库已保存到 {self.config.persist_directory}")
        else:
            print("正在加载已存在的向量数据库...")
            self.db = Chroma(
                embedding_function=self.embeddings,
                persist_directory=self.config.persist_directory
            )
            print("向量数据库加载成功")

    def get_retriever(self) -> Any:
        """获取检索器实例,用于从向量数据库中检索相关文档"""
        if self.db is None:
            raise ValueError("向量数据库未初始化")

        return self.db.as_retriever(
            search_kwargs={"k": self.config.k}  # 返回最相关的k个文档
        )

class LLMManager:
    """大语言模型类 - 管理与LLM的交互"""
    def __init__(self, config: Config):
        self.config = config
        self.llm = self._create_llm()

    def _create_llm(self) -> Any:
        """
        创建大语言模型实例
        根据配置选择不同的LLM提供商和模型
        """
        if self.config.llm_provider == "openai":
            if not self.config.openai_api_key:
                raise ValueError("OpenAI API密钥未设置")

            return OpenAI(
                model_name=self.config.llm_model,
                openai_api_key=self.config.openai_api_key,
                temperature=0.1  # 较低的温度使输出更确定性
            )
        elif self.config.llm_provider == "huggingface":
            if not self.config.huggingface_api_token:
                raise ValueError("HuggingFace API令牌未设置")

            return HuggingFaceHub(
                repo_id=self.config.llm_model,
                huggingfacehub_api_token=self.config.huggingface_api_token,
                model_kwargs={"temperature": 0.1, "max_length": 512}
            )
        elif self.config.llm_provider == "qwen":
            if not self.config.qwen_api_key:
                raise ValueError("Qwen API密钥未设置")

            return QwenLMEndpoint(
                model_name=self.config.llm_model,
                qwen_api_key=self.config.qwen_api_key,
                temperature=0.1
            )
        else:
            raise ValueError(f"不支持的LLM提供商: {self.config.llm_provider}")

class ConversationChain:
    """对话链类 - 构建完整的对话检索链"""
    def __init__(self, config: Config, retriever: Any, llm: Any):
        self.config = config
        self.llm = llm
        self.retriever = retriever
        # 使用窗口内存,保留最近的k轮对话
        self.memory = ConversationBufferWindowMemory(
            memory_key="chat_history",
            return_messages=True,
            k=config.memory_window_size  # 只保留最近的k轮对话
        )
        self.chain = self._create_chain()

    def _create_chain(self) -> Any:
        """创建对话检索链,整合LLM、检索器和记忆"""
        # 自定义提示模板,指导模型如何利用上下文和历史回答问题
        template = """
        你是一个智能助手,帮助用户解答问题。请根据以下已知信息和对话历史回答问题。
        如果已知信息不足,请直接说你不知道,不要编造信息。
        对话历史: {chat_history}
        用户问题: {question}
        已知信息: {context}
        回答:
        """

        QA_CHAIN_PROMPT = PromptTemplate(
            input_variables=["chat_history", "question", "context"],
            template=template,
        )

        return ConversationalRetrievalChain.from_llm(
            self.llm,
            retriever=self.retriever,
            memory=self.memory,
            chain_type=self.config.chain_type,
            return_source_documents=self.config.return_source_documents,
            combine_docs_chain_kwargs={"prompt": QA_CHAIN_PROMPT}
        )

    def ask(self, question: str) -> Dict[str, Any]:
        """
        向链提问并获取回答
        返回包含回答、参考资料和令牌使用量的字典
        """
        # 针对OpenAI模型,使用回调统计令牌使用量
        if self.config.llm_provider == "openai":
            with get_openai_callback() as cb:
                result = self.chain({"question": question})
            tokens_used = cb.total_tokens
        else:
            result = self.chain({"question": question})
            tokens_used = 0

        answer = result["answer"].strip()
        source_documents = result.get("source_documents", [])
        sources = []

        # 提取参考资料信息
        for doc in source_documents:
            source = os.path.basename(doc.metadata.get("source", ""))
            page = doc.metadata.get("page", "N/A")
            sources.append(f"{source} (第{page}页)" if page != "N/A" else source)

        return {
            "answer": answer,
            "sources": sources if sources else ["无"],
            "tokens_used": tokens_used
        }

class ChatInterface:
    """界面类 - 提供用户交互界面"""
    def __init__(self, conversation_chain: ConversationChain, config: Config):
        self.conversation_chain = conversation_chain
        self.config = config

    def run_cli(self) -> None:
        """运行命令行界面"""
        print("\n===== 智能对话机器人已启动 =====")
        print(f"对话记忆: 系统将保留最近的 {self.config.memory_window_size} 轮对话")
        print("输入问题开始对话,输入'退出'结束对话")

        while True:
            user_input = input("\n你: ")
            if user_input.lower() in ["退出", "q", "quit", "exit"]:
                break

            if not user_input.strip():
                continue

            response = self.conversation_chain.ask(user_input)

            print(f"\n助手: {response['answer']}")

            if response["sources"] and response["sources"][0] != "无":
                print("\n参考资料:")
                for i, source in enumerate(response["sources"], 1):
                    print(f"  {i}. {source}")

            if response["tokens_used"] > 0:
                print(f"\n本次对话使用了 {response['tokens_used']} 个令牌")

    def run_gradio(self) -> None:
        """运行Gradio Web界面"""
        def chat_interface(user_message, history):
            """Gradio聊天接口函数"""
            response = self.conversation_chain.ask(user_message)
            bot_message = response["answer"]

            # 添加参考资料信息到回答中
            if response["sources"] and response["sources"][0] != "无":
                sources_text = "\n\n参考资料:\n" + "\n".join(
                    [f"• {source}" for source in response["sources"]]
                )
                bot_message += sources_text

            history.append((user_message, bot_message))
            return history, ""

        print("\n===== Gradio Web界面已启动 =====")
        print(f"对话记忆: 系统将保留最近的 {self.config.memory_window_size} 轮对话")
        print("在浏览器中打开 http://localhost:7860 访问对话界面")

        # 构建Gradio界面
        with gr.Blocks(title="智能对话机器人") as demo:
            gr.Markdown(f"### 智能对话机器人\n对话记忆: 系统将保留最近的 {self.config.memory_window_size} 轮对话")
            chatbot = gr.Chatbot(height=500)  # 聊天界面
            msg = gr.Textbox(label="输入问题", placeholder="输入问题,按Enter发送,输入'退出'结束对话")
            clear = gr.Button("清空对话")  # 清空对话按钮

            # 设置交互逻辑
            msg.submit(chat_interface, [msg, chatbot], [chatbot, msg])
            clear.click(lambda: None, None, chatbot, queue=False)

        demo.launch()

def main():
    """主函数 - 整合所有组件,启动应用"""
    # 加载配置
    config = Config()

    try:
        # 初始化文档处理器
        doc_processor = DocumentProcessor(config)
        documents = doc_processor.load_documents()
        processed_docs = doc_processor.process_documents(documents)

        # 初始化向量数据库
        vector_db = VectorDatabase(config)
        vector_db.create_or_load_db(processed_docs)
        retriever = vector_db.get_retriever()

        # 初始化LLM管理器
        llm_manager = LLMManager(config)
        llm = llm_manager.llm

        # 初始化对话链
        conv_chain = ConversationChain(config, retriever, llm)

        # 初始化界面
        chat_interface = ChatInterface(conv_chain, config)

        # 启动界面
        if config.use_gradio:
            chat_interface.run_gradio()
        else:
            chat_interface.run_cli()

    except Exception as e:
        print(f"程序运行出错: {e}")

if __name__ == "__main__":
    main()    
— 感谢阅读 —

一起交流

分享你的思考,让讨论更进一步。