1. 项目概述与核心价值

最近在GitHub上看到一个挺有意思的项目,叫“verssache/chatgpt-creator”。光看名字,你可能会觉得这又是一个基于OpenAI API的简单封装或者UI界面。但当我真正点进去,仔细研究它的代码结构和设计理念后,发现它的野心远不止于此。这个项目的核心目标,是提供一个高度可定制、模块化的框架,让开发者能够从零开始,或者基于现有基础,构建一个属于自己的、功能完整的“ChatGPT式”对话应用。它解决的痛点非常明确:市面上很多开源聊天机器人项目要么过于庞大笨重,难以二次开发;要么过于简陋,只提供了最基础的API调用,缺乏工程化的部署、扩展和管理能力。

简单来说, chatgpt-creator 试图扮演一个“脚手架”和“工具箱”的角色。它不绑定任何特定的AI模型供应商(虽然默认示例可能使用OpenAI),而是通过清晰的接口设计,让你可以轻松接入GPT-3.5/4、Claude、国产大模型甚至是本地部署的模型。更重要的是,它关注整个对话应用的“生产级”需求:如何管理对话历史?如何实现流式输出(SSE)以提升用户体验?如何设计插件系统来扩展功能(比如联网搜索、代码执行)?如何做好错误处理和日志记录?这些才是将一个Demo级别的聊天接口,变成一个真正可用的产品所必须面对的问题。

这个项目非常适合以下几类人:一是希望深入学习大模型应用后端架构的开发者,通过拆解这个项目,你能清晰地看到一条对话请求从接收到响应的完整生命周期是如何被管理的;二是想要快速启动一个定制化智能对话项目的团队或个人,用它作为基础可以节省大量在基础设施搭建上的时间;三是对AI应用工程化感兴趣的研究者,可以借鉴其模块化设计,将自己的算法模型更方便地集成到应用流程中。接下来,我将带你深入这个项目的内部,拆解它的设计思路、核心模块,并分享如何基于它进行二次开发和避坑。

2. 项目架构与核心设计思想

2.1 模块化与松耦合设计

chatgpt-creator 最值得称道的一点是其清晰的模块化架构。它没有把所有代码堆在一个文件里,而是严格按照功能进行职责分离。通常,这类项目的核心模块会包括:

  1. 核心服务层 :这是大脑,负责处理最核心的聊天逻辑。它定义一个统一的“聊天处理器”接口,具体的实现(比如调用OpenAI的 ChatCompletion ,或调用本地模型的HTTP接口)会去实现这个接口。这种设计模式(依赖注入/策略模式)使得更换模型供应商变得异常简单,你只需要实现一个新的处理器类即可,业务逻辑代码几乎不用改动。

  2. 对话管理模块 :对话不是一次性的问答,而是有状态的上下文交互。这个模块负责维护对话会话(Session)。它会为每个用户或每个对话线程创建一个唯一的会话ID,并持久化存储相关的对话历史。存储后端可以是内存(用于开发)、Redis(用于高性能生产环境)或数据库。这个模块还定义了如何从历史记录中构建出符合模型要求的Prompt(例如,将多条历史消息组装成OpenAI API所需的 messages 数组格式)。

  3. API路由层 :提供对外的HTTP接口。通常至少会有一个 POST /chat 接口用于发送消息,并可能支持流式响应。这一层负责接收前端请求,进行基础的参数验证(如检查API Key、消息内容是否为空),然后将任务交给核心服务层处理,最后将结果封装成JSON返回给客户端。它本身不包含业务逻辑,只做协调和转发。

  4. 插件系统 :这是实现功能扩展的关键。插件可以理解为“工具”,当用户的请求涉及到特定领域时(如“查询今天的天气”、“执行一段Python代码”),聊天处理器可以决定调用某个插件。插件系统会定义标准的插件接口(例如,一个 execute 方法),每个插件独立开发,并在系统启动时被动态加载。这种设计让核心应用保持轻量,而功能可以无限扩展。

  5. 配置与工具层 :集中管理所有配置项,如模型API的密钥、基础URL、超时时间、默认参数等。通过环境变量或配置文件加载,避免硬编码。工具层则包含一些公用函数,如日志记录、异常处理、字符串处理等。

这种松耦合的设计带来的最大好处是 可维护性和可测试性 。每个模块都可以独立开发和测试。例如,你可以单独为“对话管理模块”编写单元测试,模拟各种历史记录情况,而不需要启动整个Web服务或连接真实的AI模型。

2.2 流式输出与用户体验优化

现代AI聊天应用的体验标杆就是像ChatGPT那样的“逐字输出”效果。这背后依赖的技术主要是 服务器发送事件 chatgpt-creator 项目通常会将此作为核心特性来实现。

其工作原理是:当客户端(如网页前端)发送一个聊天请求时,可以在请求头中声明接受 text/event-stream 格式的响应。服务端在接收到AI模型返回的流式数据后(例如OpenAI API返回的 stream=True 的响应),不是等所有内容都生成完毕再一次性返回,而是每收到一个数据块(chunk),就立即通过SSE协议格式( data: {chunk}\n\n )发送给客户端。前端JavaScript通过 EventSource API监听这些事件,并实时将内容追加到页面上。

在项目代码中,你会看到一个专门处理流式响应的函数或类。它需要做几件事:

  • 正确处理模型的流式响应体,按行或按特定分隔符解析。
  • 处理可能出现的错误,确保流式通道在出错时能被正确关闭,并发送一个错误事件通知前端。
  • 管理响应超时,防止连接长时间挂起。
  • (可选)在流式输出中插入一些控制信息,比如输出结束的标志 [DONE] ,或者思考过程(Chain-of-Thought)的中间标记。

实现一个健壮的流式输出并不简单,涉及到HTTP长连接管理、缓冲区处理、错误恢复等细节。 chatgpt-creator 如果在这方面做得好,会为我们提供一个非常宝贵的参考实现。

2.3 配置管理与安全性考量

一个准备投入生产环境的AI应用,安全性是重中之重。 chatgpt-creator 在配置管理上通常会采用业界最佳实践:

  • 环境变量优先 :所有敏感信息(如API密钥、数据库密码)和环境相关配置(如服务端口、日志级别)都从环境变量中读取。代码库中会提供一个 .env.example 示例文件,列出所有需要的变量,而真正的 .env 文件被添加到 .gitignore 中,避免密钥泄露。
  • API密钥鉴权 :项目可能会实现多层鉴权。最简单的是在应用层面提供一个全局的API Key,所有请求必须在Header中携带此Key。更复杂的会支持多租户,每个用户有自己的Key,并与使用量、权限等挂钩。
  • 输入输出净化与限流 :对用户输入进行必要的检查和过滤,防止Prompt注入攻击。对输出内容也可能进行安全检查(虽然主要依赖模型本身)。同时,实现请求频率限制,防止恶意刷接口导致API费用暴涨或服务瘫痪。
  • 结构化配置对象 :使用像 pydantic 这样的库来定义配置的数据模型,可以在应用启动时就完成类型验证和值校验,避免运行时配置错误。

注意 :如果你基于此项目开发,务必仔细审查其安全实现。默认配置可能仅为演示用途,在生产部署前,你必须强化认证、添加速率限制、并考虑对用户输入输出进行更严格的控制。

3. 核心模块深度解析与实操

3.1 聊天处理器:连接模型的核心

让我们深入最核心的 ChatHandler ChatService 类。一个设计良好的处理器类,其伪代码结构可能如下:

class ChatHandler:
    def __init__(self, model_client, history_manager, plugin_manager):
        self.client = model_client # 模型客户端,如OpenAI、Anthropic等
        self.history = history_manager # 对话历史管理器
        self.plugins = plugin_manager # 插件管理器

    async def handle_message(self, session_id: str, user_message: str, stream: bool = False):
        # 1. 获取或创建会话
        session = await self.history.get_or_create_session(session_id)

        # 2. 将用户消息存入历史
        session.add_message("user", user_message)

        # 3. (可选)意图识别/插件路由:判断用户消息是否需要调用插件
        plugin_to_use = self._route_to_plugin(user_message)
        if plugin_to_use:
            plugin_result = await plugin_to_use.execute(user_message)
            # 将插件执行结果作为一条“系统”或“助手”消息存入历史,并可能将其作为上下文的一部分发给模型
            session.add_message("system", f"Plugin `{plugin_to_use.name}` returned: {plugin_result}")
            # 根据插件结果,可能直接返回,也可能继续发送给模型进行总结或润色

        # 4. 从历史中构建本次请求的上下文消息列表
        #    这里涉及一个关键技巧:上下文窗口管理。
        #    模型有token限制,不能无限制发送全部历史。
        #    常见的策略是:保留最近的N轮对话,或者优先保留系统指令和最近对话,从最旧的开始裁剪直到满足token限制。
        messages_for_model = self._build_context_messages(session)

        # 5. 调用模型
        if stream:
            # 流式调用
            async for chunk in self.client.chat_completion_stream(messages=messages_for_model):
                # 处理每个chunk,可能包含内容delta、结束标志等
                processed_chunk = self._process_stream_chunk(chunk)
                yield processed_chunk
                # 同时需要累积完整响应,以便最后存入历史
                self._accumulate_response(processed_chunk)
            full_response = self._get_accumulated_response()
        else:
            # 非流式调用
            response = await self.client.chat_completion(messages=messages_for_model)
            full_response = response.choices[0].message.content

        # 6. 将模型响应存入历史
        session.add_message("assistant", full_response)

        # 7. 返回响应(流式模式下已在yield中返回)
        if not stream:
            return full_response

关键点解析

  • 上下文构建 ( _build_context_messages ) :这是影响对话连贯性和智能性的核心。除了简单的历史拼接,高级的实现还会加入“系统指令”,用来设定AI的角色、行为规范和知识边界。例如, messages_for_model 的开头可能是一条固定的系统消息:“你是一个有帮助的助手,回答要简洁准确。”
  • Token计算与裁剪 :为了精确控制上下文长度,需要集成一个tokenizer(例如OpenAI的 tiktoken 或通用的 transformers 库)。在构建消息前,计算当前历史的总token数,如果超过模型上限(如GPT-4的8192),则按照策略进行裁剪。这是一个容易出错的环节,裁剪策略不当会导致对话逻辑断裂。
  • 插件集成点 :插件可以在模型调用前(预处理)、调用后(后处理)介入。上述示例是在调用前,根据用户意图直接执行插件。另一种模式是“函数调用”,让模型自己决定何时、调用哪个插件,这需要模型支持 function calling 特性,并在请求时提供插件(函数)的描述。

3.2 对话历史管理的实现策略

对话历史管理模块 ( HistoryManager ) 负责状态的持久化。其接口设计通常包括:

  • get_session(session_id) -> Session
  • create_session(session_id, initial_system_prompt=None) -> Session
  • add_message(session_id, role, content)
  • clear_history(session_id)

存储后端的选择与实现

  1. 内存存储 :最简单的实现,用一个字典在内存中保存所有会话。缺点显而易见:服务重启数据丢失,无法分布式部署。仅适用于开发测试。

    class InMemoryHistoryManager:
        def __init__(self):
            self.sessions = {} # {session_id: Session}
    
        async def get_session(self, session_id):
            return self.sessions.get(session_id)
    
  2. Redis存储 :生产环境的常见选择。利用Redis的 list hash 数据结构存储消息,并设置过期时间。优点是性能极高,支持分布式。需要处理序列化(通常用JSON或MessagePack)。

    import redis.asyncio as redis
    import json
    
    class RedisHistoryManager:
        def __init__(self, redis_url):
            self.client = redis.from_url(redis_url)
    
        async def add_message(self, session_id, role, content):
            key = f"chat:session:{session_id}:messages"
            message = json.dumps({"role": role, "content": content, "timestamp": time.time()})
            await self.client.rpush(key, message)
            await self.client.expire(key, 3600*24*7) # 设置7天过期
    
  3. 数据库存储 :如果需要永久保存、复杂查询或数据分析,可以选择SQL(如PostgreSQL)或NoSQL(如MongoDB)数据库。表结构设计可能包含 session_id , role , content , created_at 等字段。性能相比Redis有差距,但功能更强大。

实操心得

  • 会话过期 :无论是Redis还是数据库,都应为会话数据设置TTL(生存时间)。用户可能中途离开,永不回来的会话数据应被自动清理,避免存储膨胀。
  • 消息格式 :存储时除了 role content ,强烈建议加上 timestamp 。这对于调试、按时间排序、以及实现“最近N小时内对话”这类高级上下文管理策略非常有用。
  • 性能考量 :频繁的读写数据库可能成为瓶颈。一个优化策略是使用“写缓存”:先将新消息写入一个高速缓存(如Redis),然后异步批量同步到数据库。读取时,优先从缓存读取,缓存缺失再查库。

3.3 插件系统的设计与扩展

插件系统是让聊天机器人“无所不能”的钥匙。 chatgpt-creator 的插件系统设计,通常包含以下几个部分:

  1. 插件基类 :定义一个所有插件都必须实现的接口。

    from abc import ABC, abstractmethod
    from typing import Any, Dict
    
    class BasePlugin(ABC):
        @property
        def name(self) -> str:
            """插件唯一标识名"""
            pass
    
        @property
        def description(self) -> str:
            """插件功能描述,用于告知模型或用户"""
            pass
    
        @abstractmethod
        async def execute(self, input_text: str, **kwargs) -> Dict[str, Any]:
            """执行插件核心逻辑"""
            pass
    
  2. 插件管理器 :负责插件的加载、注册和路由。

    class PluginManager:
        def __init__(self):
            self.plugins = {}
    
        def register_plugin(self, plugin: BasePlugin):
            self.plugins[plugin.name] = plugin
    
        def get_plugin(self, name: str) -> BasePlugin:
            return self.plugins.get(name)
    
        def route(self, user_input: str) -> BasePlugin:
            """根据用户输入,决定调用哪个插件。这里可以实现简单的关键词匹配或更复杂的意图识别模型。"""
            for plugin in self.plugins.values():
                if any(keyword in user_input.lower() for keyword in plugin.keywords):
                    return plugin
            return None
    
  3. 具体插件示例 :比如一个天气查询插件。

    import aiohttp
    
    class WeatherPlugin(BasePlugin):
        name = "weather"
        description = "查询指定城市的当前天气情况。"
        keywords = ["天气", "weather", "气温"]
    
        def __init__(self, api_key: str):
            self.api_key = api_key
    
        async def execute(self, input_text: str, **kwargs) -> Dict[str, Any]:
            # 1. 从输入文本中提取城市名(这里简化处理,实际可用NLP模型)
            city = self._extract_city(input_text)
            if not city:
                return {"error": "未识别出城市名"}
    
            # 2. 调用外部天气API
            url = f"https://api.weatherapi.com/v1/current.json?key={self.api_key}&q={city}"
            async with aiohttp.ClientSession() as session:
                async with session.get(url) as resp:
                    data = await resp.json()
    
            # 3. 格式化结果
            if 'current' in data:
                return {
                    "city": data['location']['name'],
                    "temp_c": data['current']['temp_c'],
                    "condition": data['current']['condition']['text']
                }
            else:
                return {"error": "获取天气信息失败"}
    

如何集成插件到聊天流程 ? 有两种主流模式:

  • 显式路由 :如上文 ChatHandler 示例,在调用模型前,由 PluginManager.route 方法根据关键词决定是否触发插件。触发后,将插件执行结果作为上下文的一部分,再交给模型生成最终回复。这种方式控制性强,但不够灵活。
  • 模型函数调用 :这是更先进的方式。在请求模型时,将插件的描述(作为“函数”或“工具”定义)一并发送给模型。模型根据对话上下文,自主决定是否需要调用插件,并返回一个包含插件调用参数的JSON。服务端收到后,执行对应插件,再将结果返回给模型,由模型整合成自然语言回复给用户。这需要模型API的支持(如OpenAI的 function calling )。

4. 部署、优化与生产环境考量

4.1 从开发到生产:部署实践

一个完整的 chatgpt-creitor 项目部署到生产环境,需要考虑以下环节:

1. 环境准备与配置:

  • 配置文件 :将开发用的 .env 文件转化为生产环境配置。确保所有API密钥、数据库连接字符串等都已更新。
  • 依赖管理 :使用 requirements.txt Pipfile 精确锁定所有Python包版本,避免因依赖更新导致的不兼容。
  • 日志配置 :配置生产级日志,如使用 structlog logging 模块,将日志输出到文件(如JSON格式),并设置合理的日志轮转策略和级别(生产环境通常用 INFO WARNING )。

2. 服务进程管理:

  • ASGI服务器 :如果项目使用FastAPI或Starlette等异步框架,需要使用ASGI服务器来运行,如 uvicorn hypercorn daphne 。不建议直接使用开发服务器。
    # 使用uvicorn启动,设置工作进程数,绑定到非公开端口,并通过Nginx反向代理
    uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
    
  • 进程守护 :使用 systemd supervisord 来管理服务进程,确保服务崩溃后能自动重启。
  • 容器化部署 :强烈推荐使用Docker。编写 Dockerfile ,将应用及其依赖打包成镜像。这能保证环境一致性,简化部署。
    FROM python:3.11-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    COPY . .
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
    

3. 网络与安全:

  • 反向代理 :使用Nginx或Caddy作为反向代理,处理SSL/TLS终止、静态文件服务、负载均衡和基本的请求过滤。将应用服务器(如uvicorn)运行在内部端口(如8000),通过Nginx暴露到外网的443端口。
  • 防火墙与安全组 :确保服务器防火墙只开放必要的端口(如80, 443, 22)。
  • API网关与鉴权 :对于企业级应用,可以考虑在前置层增加API网关(如Kong, Tyk),统一处理认证、限流、监控等跨领域关切。

4.2 性能监控与问题排查

应用上线后,监控是发现和解决问题的眼睛。

关键监控指标:

  • 应用性能 :请求延迟(P50, P95, P99)、每秒查询率、错误率。可以使用 Prometheus 客户端库在代码中暴露指标,并用 Grafana 展示。
  • 资源使用 :服务器CPU、内存、磁盘I/O、网络带宽。云服务商的控制台通常提供这些数据。
  • 模型API使用 :记录每次调用模型API的token消耗(输入+输出)、费用、响应时间。这有助于成本控制和性能分析。
  • 业务指标 :活跃会话数、平均对话轮次、插件调用频率等。

日志与追踪:

  • 结构化日志 :确保每条日志都包含请求ID、会话ID、用户ID(如果存在)、时间戳、日志级别和关键上下文信息。这样在排查问题时,可以通过一个请求ID串联起所有相关日志。
  • 分布式追踪 :如果服务拆分成多个微服务,需要引入如Jaeger或Zipkin这样的分布式追踪系统,可视化一个请求流经的所有服务。

常见问题排查清单:

问题现象 可能原因 排查步骤
聊天响应慢 1. 模型API响应慢
2. 自身服务处理瓶颈
3. 网络延迟
1. 检查模型API状态页。
2. 查看服务监控,检查CPU/内存是否吃紧,是否有慢查询(如数据库)。
3. 使用 curl postman 测试接口,分别记录总耗时和服务内部耗时。
流式输出中断 1. 网络连接不稳定
2. 服务端处理流式响应时异常
3. 客户端超时设置过短
1. 查看服务端日志,是否有未捕获的异常。
2. 检查Nginx等代理的超时配置(特别是 proxy_read_timeout )。
3. 在前端增加重连机制和错误提示。
对话上下文丢失 1. 会话存储失败
2. 会话ID生成或传递错误
3. 存储服务(如Redis)故障
1. 检查会话存储后端的连接状态和日志。
2. 确认前端在同一个会话中是否始终发送相同的 session_id
3. 检查存储的TTL设置是否过短。
插件调用失败 1. 插件依赖的外部API不可用
2. 插件代码逻辑错误
3. 输入参数解析失败
1. 查看插件执行日志,确认错误信息。
2. 测试插件独立运行是否正常。
3. 检查插件路由逻辑,确认用户输入是否被正确解析。

4.3 成本控制与优化策略

使用商用大模型API,成本是必须考虑的因素。 chatgpt-creator 项目本身不产生成本,但集成的模型API会。

成本控制策略:

  1. Token使用优化

    • 上下文管理 :如前所述,实现智能的上下文窗口裁剪,只保留最相关的历史消息。可以尝试不同的策略,如“仅保留最近5轮对话”或“优先保留系统指令和最近对话”。
    • 总结与压缩 :对于长对话,可以引入一个“总结”步骤。当历史记录过长时,调用模型(可以用更便宜的模型如 gpt-3.5-turbo )将之前的对话总结成一段简短的摘要,然后用摘要替代旧的历史,从而大幅减少token消耗。
    • 设置最大输出token :在调用API时,明确设置 max_tokens 参数,防止模型生成过于冗长的回答。
  2. 缓存策略

    • 回答缓存 :对于常见、确定性的问题(如“你是谁?”、“怎么使用?”),可以将标准答案缓存起来,直接返回,避免调用模型。可以使用Redis,以问题的哈希值为Key进行缓存。
    • 嵌入缓存 :如果使用了向量检索等涉及嵌入向量的功能,计算嵌入是昂贵的。可以将文本-嵌入对缓存起来。
  3. 模型选型

    • 并非所有任务都需要最强大的模型。可以根据对话的复杂度进行动态路由。例如,简单问答使用 gpt-3.5-turbo ,复杂推理或创作再使用 gpt-4
    • 积极关注和测试性价比更高的模型,如Claude Haiku、国产大模型等。
  4. 用量监控与告警

    • 务必在模型供应商的后台设置用量告警和预算硬限制。
    • 在自己的应用层面,记录每个用户/每个会话的token消耗,对异常高的使用进行预警或限速。

5. 二次开发与定制化指南

5.1 接入自定义或本地模型

chatgpt-creator 的魅力在于其可扩展性。如果你想接入非OpenAI的模型,例如本地部署的Llama 3、Qwen,或者百度的文心一言、阿里的通义千问,只需要实现对应的“模型客户端”即可。

步骤:

  1. 研究目标模型API :查看其官方文档,了解其聊天补全接口的请求格式、响应格式、认证方式等。
  2. 创建客户端类 :这个类需要实现项目约定的一个抽象接口(如果项目有的话),或者至少提供一个与现有 OpenAIClient 类似的方法,例如 chat_completion(messages, stream=False)
  3. 处理格式转换 :不同模型的API格式可能不同。你的客户端类需要负责将内部统一的 messages 格式(通常是 [{"role": "user", "content": "..."}] )转换为目标API要求的格式,同时将返回的响应转换回内部统一的格式。
  4. 集成流式支持 :如果目标模型支持流式响应,在你的客户端中实现 chat_completion_stream 方法,以异步生成器的形式返回数据块。
  5. 注册到系统 :在应用初始化时,用你的自定义客户端实例替换掉默认的OpenAI客户端。

示例:接入一个假设的本地HTTP模型服务

import aiohttp
import json

class LocalModelClient:
    def __init__(self, base_url: str):
        self.base_url = base_url.rstrip('/')

    async def chat_completion(self, messages, **kwargs):
        # 将内部格式转换为本地模型API格式
        payload = {
            "inputs": messages,
            "parameters": {
                "max_new_tokens": kwargs.get('max_tokens', 512),
                "temperature": kwargs.get('temperature', 0.7)
            }
        }
        async with aiohttp.ClientSession() as session:
            async with session.post(f"{self.base_url}/generate", json=payload) as resp:
                result = await resp.json()
                # 将响应转换回内部格式
                return {"choices": [{"message": {"content": result.get("generated_text", "")}}]}

    # 流式支持类似,需要处理chunked response

5.2 开发自定义插件

开发新插件是扩展机器人能力最直接的方式。以“待办事项管理插件”为例:

  1. 定义插件功能 :明确插件做什么。例如,识别用户关于“添加待办”、“查看我的待办”、“标记完成”的指令,并操作一个后端存储(如数据库或文件)。
  2. 创建插件类 :继承自 BasePlugin ,实现 name , description , keywords 属性和 execute 方法。
  3. 实现业务逻辑 :在 execute 方法中,解析用户输入,执行增删改查操作。注意做好错误处理。
  4. 设计数据存储 :为简单起见,可以用一个SQLite数据库或JSON文件来存储每个用户的待办事项。更正式的做法是连接到一个独立的待办事项服务。
  5. 注册插件 :在应用启动脚本中,实例化你的插件并将其注册到 PluginManager

一个进阶技巧:插件描述与自动发现 为了让模型能更好地理解和使用插件(在函数调用模式下),你需要为插件编写清晰、结构化的描述。这通常是一个JSON Schema,定义了插件的名称、描述、所需参数等。你可以将这个描述动态加载到系统,并在请求模型时提供给模型。

5.3 前端界面集成

chatgpt-creator 项目主要关注后端。要提供一个完整的聊天产品,你需要一个前端界面。

选择与集成策略:

  1. 使用现成前端 :GitHub上有许多优秀的开源ChatGPT风格前端,如 chatbot-ui , chatgpt-next-web 等。这些前端通常提供可配置的API端点。你只需要将你的后端服务地址配置进去即可。这是最快的方式。
  2. 自行开发简单界面 :如果你只需要一个基础界面,可以使用HTML/JS快速搭建。核心是使用 EventSource Fetch API 与后端的流式接口通信。
    // 前端调用流式接口的简化示例
    async function sendMessage() {
        const input = document.getElementById('userInput').value;
        const sessionId = 'user-session-123'; // 需要持久化或由后端生成
        const eventSource = new EventSource(`/chat/stream?session_id=${sessionId}&message=${encodeURIComponent(input)}`);
    
        eventSource.onmessage = function(event) {
            const data = JSON.parse(event.data);
            if (data.content) {
                // 将内容逐字追加到聊天窗口
                appendToChatWindow('assistant', data.content);
            }
            if (data.finish_reason) {
                eventSource.close();
            }
        };
        eventSource.onerror = function(err) {
            console.error('EventSource failed:', err);
            eventSource.close();
        };
    }
    
  3. 深度定制 :如果你有特定的UI/UX需求,可以使用React、Vue等现代前端框架从头开发。后端只需提供清晰、稳定的API。

部署一体化 :在Docker部署时,可以将构建好的前端静态文件(如 dist 目录)放到后端的静态文件目录下,由Nginx或后端框架本身(如FastAPI的 StaticFiles )提供服务,实现前后端一体化部署。

更多推荐