1. 理解需求
在开始编码之前,确保你完全理解新的后端需求。这包括:
- 功能需求:明确需要实现什么功能。
- 数据需求:确定是否需要新的数据库模型或数据迁移。
- 接口需求:是否需要新增或修改 API 端点。
- 集成需求:是否需要与现有的服务、组件或第三方工具集成。
2. 熟悉项目结构
从你提供的目录结构来看,项目主要分为以下几个部分:
.github:GitHub 工作流和 CI/CD 配置。docker:Docker 配置和容器化相关内容。src/backend:后端代码,包含 API、服务、数据库模型等。src/bisheng-langchain:可能与 LangChain 相关的扩展或自定义功能。frontend:前端代码。
重点关注 src/backend 目录,因为你要进行后端开发。
3. 确定开发位置
根据需求,确定新功能应该添加到项目的哪个部分。以下是一些常见的情况:
- 新增 API 端点:
- 路径:
src/backend/bisheng/api/ - 具体版本目录,如
v1或v2,根据需要选择。 - 示例:如果要新增一个用户管理的 API,可以在
src/backend/bisheng/api/v1/下创建相应的模块。
- 路径:
- 新增服务逻辑:
- 路径:
src/backend/bisheng/services/ - 创建新的服务文件或在现有服务中添加逻辑。
- 示例:如果需要一个新的数据处理服务,可以在
services目录下创建data_processing.py。
- 路径:
- 数据库模型:
- 路径:
src/backend/bisheng/database/models/ - 添加新的 ORM 模型或修改现有模型。
- 示例:新增一个
User模型,可以在models目录下创建user.py。
- 路径:
- 缓存处理:
- 路径:
src/backend/bisheng/cache/ - 如果需求涉及缓存,可以在这里添加或修改缓存逻辑。
- 路径:
- 图相关功能:
- 路径:
src/backend/bisheng/graph/ - 适用于涉及图数据库或图算法的功能。
- 路径:
- 接口相关功能(如 Agents, Chains 等):
- 路径:
src/backend/bisheng/interface/ - 适用于与 LangChain 或其他接口相关的功能扩展。
- 路径:
4. 创建和编写代码
根据确定的位置,开始编写代码:
-
新增文件:在相应的目录下创建新的 Python 文件。例如,
src/backend/bisheng/services/new_service.py。 -
遵循项目规范:
- 编码风格:遵循 PEP 8 或团队规定的编码规范。
- 命名约定:使用清晰且一致的命名方式。
- 文档和注释:为函数和类添加适当的文档字符串和注释,便于团队其他成员理解。
-
示例:新增一个简单的服务
# src/backend/bisheng/services/new_service.py from typing import Any class NewService: def perform_action(self, data: Any) -> Any: """ 执行某个动作的示例方法 """ # 你的业务逻辑 result = data # 示例处理 return result
5. 集成到现有架构
-
API 端点:如果你新增了服务,需要在 API 层进行集成。
# src/backend/bisheng/api/v1/new_endpoint.py from fastapi import APIRouter, HTTPException from ..services.new_service import NewService router = APIRouter() service = NewService() @router.post("/new-action") async def new_action_endpoint(payload: dict): try: result = service.perform_action(payload) return {"status": "success", "data": result} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) -
路由注册:确保新的 API 端点在主路由中注册。
# src/backend/bisheng/api/v1/__init__.py from .new_endpoint import router as new_endpoint_router # 其他路由...
6. 数据库迁移(如果需要)
如果你的功能涉及到数据库模型的变更:
-
创建迁移脚本:使用 ORM 工具(如 SQLAlchemy、Alembic)创建数据库迁移脚本。
alembic revision --autogenerate -m "Add User model" alembic upgrade head -
更新模型:
# src/backend/bisheng/database/models/user.py from sqlalchemy import Column, Integer, String from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True) email = Column(String, unique=True, index=True)
7. 编写测试
确保新功能有相应的测试覆盖:
-
路径:
src/backend/bisheng/test/ -
编写单元测试:
# src/backend/bisheng/test/test_new_service.py import pytest from bisheng.services.new_service import NewService def test_perform_action(): service = NewService() input_data = {"key": "value"} result = service.perform_action(input_data) assert result == input_data # 根据实际逻辑调整断言 -
运行测试:
pytest
8. 配置和环境变量
如果新功能需要特定的配置或环境变量:
- 配置文件:检查
docker/bisheng/config/或项目的配置管理方式,添加必要的配置项。 - 环境变量:在
.env文件或 Docker 配置中添加新的环境变量。
9. 本地测试与调试
-
启动本地开发环境:使用 Docker 或其他工具启动项目,确保新功能在本地运行良好。
docker-compose up --build -
调试:使用 IDE 的调试工具,逐步检查代码执行情况,确保没有逻辑错误。
10. 代码审查与提交
- 代码审查:根据团队的 Git 流程,创建一个新的分支,提交代码并发起 Pull Request (PR)。
- 描述清晰:在 PR 中详细描述所做的更改、新增的功能及其用途。
- 响应反馈:根据团队成员的反馈进行必要的修改和优化。
11. 部署
在代码审核通过后,按照团队的部署流程将新功能部署到生产环境。这可能包括:
- CI/CD 管道:通过
.github/workflows配置的 GitHub Actions 或其他 CI/CD 工具自动化部署。 - 监控和日志:确保新功能上线后有适当的监控和日志记录,以便及时发现和解决问题。
12. 文档更新
- API 文档:如果新增了 API 端点,更新 API 文档(如使用 Swagger、Redoc 等工具)。
- 开发文档:记录新功能的实现细节、使用方法和注意事项,帮助团队其他成员理解和使用。
总结
以下是一个简化的流程图,以便更好地理解:
- 需求分析 → 2. 熟悉项目结构 → 3. 确定开发位置 → 4. 编写代码 → 5. 集成到架构 → 6. 数据库迁移 → 7. 编写测试 → 8. 配置环境 → 9. 本地测试 → 10. 代码审查 → 11. 部署 → 12. 文档更新
通过上述步骤,你可以系统地在现有项目中添加新的后端功能。记住,沟通也是关键,定期与团队成员交流,确保你的开发方向与团队目标一致。
如果你对项目的某些部分仍有疑问,建议:
- 查阅项目文档:查看项目的 README、开发指南或内部文档。
- 咨询团队成员:向经验丰富的同事寻求指导,他们可以提供具体的建议和最佳实践。
- 代码阅读:通过阅读现有代码,了解项目的实现方式和编码规范。
chat.py
src/backend/bisheng/api/
1. 文件概述
chat.py 是后端 API 的一个模块,主要负责处理与聊天相关的请求。它定义了一系列的路由(Endpoints),包括发送聊天消息、获取聊天列表、管理聊天记录等功能。
2. 导入部分
首先,我们来看一下文件顶部的导入部分:
import json
import math
from typing import List, Optional
from uuid import UUID, uuid1
# 自定义模块导入
from bisheng.api.errcode.base import UnAuthorizedError
from bisheng.api.services import chat_imp
from bisheng.api.services.assistant import AssistantService
from bisheng.api.services.audit_log import AuditLogService
from bisheng.api.services.base import BaseService
from bisheng.api.services.chat_imp import comment_answer
from bisheng.api.services.flow import FlowService
from bisheng.api.services.knowledge_imp import delete_es, delete_vector
from bisheng.api.services.user_service import UserPayload, get_login_user
from bisheng.api.utils import build_flow, build_input_keys_response, get_request_ip
from bisheng.api.v1.schema.base_schema import PageList
from bisheng.api.v1.schema.chat_schema import APIChatCompletion, AppChatList
from bisheng.api.v1.schemas import (
AddChatMessages, BuildStatus, BuiltResponse, ChatInput,
ChatList, FlowGptsOnlineList, InitResponse, StreamData,
UnifiedResponseModel, resp_200
)
from bisheng.cache.redis import redis_client
from bisheng.chat.manager import ChatManager
from bisheng.database.base import session_getter
from bisheng.database.models.assistant import AssistantDao, AssistantStatus
from bisheng.database.models.flow import Flow, FlowDao, FlowStatus
from bisheng.database.models.flow_version import FlowVersionDao
from bisheng.database.models.group_resource import GroupResourceDao, ResourceTypeEnum
from bisheng.database.models.mark_record import MarkRecordDao
from bisheng.database.models.mark_task import MarkTaskDao
from bisheng.database.models.message import ChatMessage, ChatMessageDao, ChatMessageRead, MessageDao
from bisheng.database.models.user import UserDao
from bisheng.database.models.user_group import UserGroupDao
from bisheng.graph.graph.base import Graph
from bisheng.utils.logger import logger
from bisheng.utils.util import get_cache_key
from fastapi import (
APIRouter, Body, HTTPException, Query, Request, WebSocket,
WebSocketException, status
)
from fastapi.params import Depends
from fastapi.responses import StreamingResponse
from fastapi_jwt_auth import AuthJWT
from sqlalchemy import func
from sqlmodel import select
主要导入内容:
- 标准库:如
json,math,uuid等。 - FastAPI 框架相关:如
APIRouter,Depends,HTTPException等。 - 自定义模块:大量导入了项目内部的服务、模型、工具等模块,这些模块位于
bisheng.api.services、bisheng.database.models等目录下。
3. 路由和管理器初始化
router = APIRouter(tags=['Chat'])
chat_manager = ChatManager()
flow_data_store = redis_client
expire = 600 # redis 过期时间,单位秒
router:使用 FastAPI 的APIRouter定义一个带有标签Chat的路由组。chat_manager:初始化一个ChatManager实例,用于管理聊天会话。flow_data_store:使用 Redis 作为数据存储,设置了缓存过期时间为 600 秒。
4. 路由定义
4.1. /chat/completions - 聊天完成流
@router.post('/chat/completions', response_class=StreamingResponse)
async def chat_completions(request: APIChatCompletion, Authorize: AuthJWT = Depends()):
# 处理聊天完成请求,返回流式响应
# 具体逻辑略
return StreamingResponse(...)
功能:处理聊天完成请求,返回流式响应。
涉及服务文件:
bisheng.api.services.chat_imp:用于处理聊天连接和事件流。
4.2. /chat/app/list - 获取聊天应用列表
@router.get('/chat/app/list', response_model=UnifiedResponseModel[PageList[AppChatList]], status_code=200)
def get_app_chat_list(...):
# 通过消息表进行聊天App统计
# 具体逻辑略
return resp_200(res_obj)
功能:获取聊天应用列表,支持分页、筛选等功能。
涉及服务文件:
bisheng.api.services.user_service:用于获取登录用户信息。bisheng.database.models:多个 DAO 文件,用于数据库操作。
4.3. /chat/history - 获取聊天历史
@router.get('/chat/history', response_model=UnifiedResponseModel[List[ChatMessageRead]], status_code=200)
def get_chatmessage(...):
# 获取聊天历史消息
# 具体逻辑略
return resp_200(db_message)
功能:获取特定聊天会话的历史消息。
涉及服务文件:
bisheng.database.models.message:ChatMessageDao用于操作聊天消息。
4.4. /chat/{chat_id} - 删除聊天会话
@router.delete('/chat/{chat_id}', status_code=200)
def del_chat_id(...):
# 删除指定 chat_id 的聊天记录
# 具体逻辑略
return resp_200(message='删除成功')
功能:删除指定 chat_id 的聊天记录,并处理相关的临时数据和审计日志。
涉及服务文件:
bisheng.api.services.knowledge_imp:用于删除向量和 Elasticsearch 数据。bisheng.api.services.audit_log:用于记录审计日志。bisheng.database.models.message:用于删除消息。
4.5. /chat/message - 添加聊天消息
@router.post('/chat/message', status_code=200)
def add_chat_messages(...):
# 添加一条完整的问答记录
# 具体逻辑略
return resp_200(message='添加成功')
功能:添加一条完整的问答记录,包含人类消息和机器人回复。
涉及服务文件:
bisheng.database.models.message:ChatMessageDao用于插入和获取消息。bisheng.api.services.audit_log:用于记录审计日志。
4.6. /chat/message/{message_id} - 更新和删除消息
@router.put('/chat/message/{message_id}', status_code=200)
def update_chat_message(...):
# 更新一条消息的内容
# 具体逻辑略
return resp_200(message='更新成功')
@router.delete('/chat/message/{message_id}', status_code=200)
def del_message_id(...):
# 删除一条消息
# 具体逻辑略
return resp_200(message='删除成功')
功能:更新和删除特定 message_id 的消息。
涉及服务文件:
bisheng.database.models.message:ChatMessageDao用于更新和删除消息。
4.7. 其他路由
/liked:点赞响应。/chat/copied:复制消息。/chat/comment:评论消息。/chat/list:获取聊天列表。/chat/online:获取所有已上线的技能和助手。- WebSocket
/chat/{flow_id}:处理实时聊天会话。 /build/init/{flow_id}:初始化构建流程。/build/{flow_id}/status:检查构建状态。/build/stream/{flow_id}:流式构建流程。
功能:这些路由涵盖了聊天互动、消息管理、构建流程等多个方面。
涉及服务文件:
- 多个服务文件,包括
assistant,flow,chat_imp,audit_log,knowledge_imp等。
5. 实现某个功能需要的服务文件
基于 chat.py 中定义的多个路由,我们可以看到它依赖了多个服务文件。以下是一些主要的服务文件及其职责:
bisheng.api.services.chat_imp- 职责:处理聊天连接和事件流,管理 WebSocket 连接。
- 相关功能:
get_connection,event_stream,comment_answer等。
bisheng.api.services.assistant- 职责:管理助手(Assistant)的相关逻辑。
- 相关功能:获取助手列表、状态管理等。
bisheng.api.services.audit_log- 职责:记录审计日志,跟踪用户操作。
- 相关功能:
create_chat_flow,delete_chat_flow,create_chat_assistant,delete_chat_assistant等。
bisheng.api.services.flow- 职责:管理技能(Flow)的相关逻辑。
- 相关功能:获取技能列表、状态管理等。
bisheng.api.services.knowledge_imp- 职责:处理知识库相关操作,如删除向量和 Elasticsearch 数据。
- 相关功能:
delete_es,delete_vector等。
bisheng.api.services.user_service- 职责:管理用户相关逻辑。
- 相关功能:
get_login_user等。
bisheng.api.services.base- 职责:提供基础服务功能,如获取 logo 链接。
- 相关功能:
get_logo_share_link等。
bisheng.api.utils- 职责:提供工具函数,辅助构建流程和处理请求。
- 相关功能:
build_flow,build_input_keys_response,get_request_ip等。
6. 下一步操作
根据上述分析,当你需要实现或修改某个功能时,可以按照以下步骤进行:
- 确定功能需求:明确你需要实现什么功能,例如新增一个聊天端点、修改消息处理逻辑等。
- 定位相关路由:在
chat.py中找到对应的路由或需要新增的路由。 - 识别依赖的服务文件:根据路由逻辑,确定需要涉及哪些服务文件。例如,若是处理聊天消息,可能需要
chat_imp、message模型等。 - 逐步解析和实现:
- 提供相关服务文件:根据依赖关系,提供相应的服务文件内容,例如
chat_imp.py。 - 讲解服务文件逻辑:我将帮助你理解这些服务文件的实现方式和它们如何与 API 端点交互。
- 逐步实现功能:结合 API 和服务文件,逐步实现或修改功能,确保每一步都清晰明了。
- 提供相关服务文件:根据依赖关系,提供相应的服务文件内容,例如
示例:新增一个聊天记录搜索功能
假设你需要新增一个功能,允许用户通过关键词搜索聊天记录。以下是一个可能的实现流程:
-
定义路由:在
chat.py中新增一个路由,如/chat/search。@router.get('/chat/search', response_model=UnifiedResponseModel[List[ChatMessageRead]], status_code=200) def search_chat_messages( keyword: str, page: Optional[int] = 1, page_size: Optional[int] = 20, login_user: UserPayload = Depends(get_login_user) ): # 调用服务层搜索聊天消息 return resp_200(ChatMessageDao.search_messages(keyword, login_user.user_id, page, page_size)) -
实现搜索逻辑:在
bisheng.database.models.message下的ChatMessageDao类中新增search_messages方法。class ChatMessageDao: @staticmethod def search_messages(keyword: str, user_id: int, page: int, page_size: int) -> List[ChatMessageRead]: with session_getter() as session: stmt = select(ChatMessage).where( ChatMessage.user_id == user_id, ChatMessage.message.contains(keyword) ).offset((page - 1) * page_size).limit(page_size) return session.exec(stmt).all() -
编写测试:在
src/backend/bisheng/test/下新增相应的测试用例,确保搜索功能正常。def test_search_chat_messages(): # 模拟用户登录 user_payload = UserPayload(user_id=1, user_name='test_user', role='admin') # 添加测试数据 ChatMessageDao.insert_batch([ ChatMessage(flow_id='flow1', chat_id='chat1', user_id=1, message='Hello World'), ChatMessage(flow_id='flow1', chat_id='chat2', user_id=1, message='Python is awesome') ]) # 搜索关键词 result = ChatMessageDao.search_messages('Python', 1, 1, 10) assert len(result) == 1 assert result[0].message == 'Python is awesome' -
提交代码并进行代码审查:创建一个新的分支,提交代码并发起 Pull Request,等待团队成员的审查和反馈。
-
部署和验证:通过 CI/CD 流程部署新功能,并在生产环境中进行验证,确保功能按预期工作。
总结
通过上述分析,你可以看到一个 API 端点通常依赖于多个服务文件和数据库模型。理解这些依赖关系,并逐步解析每个组件的职责,是实现和维护功能的关键。
下一步建议:
- 选择一个具体功能:例如,新增一个聊天记录搜索功能。
- 提供相关服务文件的代码:例如,
chat_imp.py或其他相关文件。 - 我将帮助你逐步解析这些服务文件,并指导你完成功能的实现。
请提供你希望深入了解的下一个服务文件的代码,例如 src/backend/bisheng/services/chat_imp.py,我们将继续讲解。
chat_imp.py
src/backend/bisheng/api/services/chat_imp.py
好的,让我们继续深入解析 src/backend/bisheng/api/services/chat_imp.py 文件,并了解它在整个项目中的作用和实现细节。随后,我将指导你了解实现这个功能所需的其他文件。
1. 文件概述
chat_imp.py 主要负责管理与聊天相关的 WebSocket 连接和事件流。它包括连接池管理、WebSocket 连接的获取和释放、消息的处理等功能。这些功能支持 chat.py 中定义的各种聊天相关 API 端点。
2. 导入部分
import asyncio
import json
import logging
from collections import defaultdict
from datetime import datetime, timedelta
from bisheng.api.v1.schemas import resp_500
from bisheng.database.base import session_getter
from bisheng.database.models.message import ChatMessage
from pydantic import BaseModel
from websockets import connect
主要导入内容:
- 标准库:
asyncio:用于异步编程,管理协程和事件循环。json:处理 JSON 数据。logging:日志记录。defaultdict:提供默认值的字典。datetime和timedelta:处理日期和时间。
- 项目内部模块:
resp_500:统一的 500 错误响应模型。session_getter:数据库会话获取工具。ChatMessage:聊天消息的数据库模型。BaseModel:Pydantic 基类,用于数据验证和序列化。connect:WebSocket 连接库。
3. 连接池管理
3.1. 连接池初始化
# 维护一个连接池
connection_pool = defaultdict(asyncio.Queue)
logging.getLogger('websockets').setLevel(logging.ERROR)
expire = 600 # redis 60s 过期
connection_pool:使用defaultdict和asyncio.Queue维护一个 WebSocket 连接池。每个identifier对应一个队列,存储多个 WebSocket 连接。- 日志级别设置:将
websockets模块的日志级别设置为ERROR,减少不必要的日志输出。 expire:设置连接池中连接的过期时间为 600 秒(10 分钟)。
3.2. TimedQueue 类
class TimedQueue:
def __init__(self):
self.queue = asyncio.Queue()
self.last_active = datetime.now()
async def put_nowait(self, item):
self.last_active = datetime.now()
await self.queue.put(item)
async def get_nowait(self):
self.last_active = datetime.now()
return await self.queue.get()
def empty(self):
return self.queue.empty()
def qsize(self):
return self.queue.qsize()
功能:
TimedQueue:封装了一个asyncio.Queue,并记录队列的最后活跃时间 (last_active)。每次放入或获取元素时,更新last_active。
3.3. 清理不活跃队列
async def clean_inactive_queues(queue: defaultdict, timeout_threshold: timedelta):
while True:
current_time = datetime.now()
for key, timed_queue in list(queue.items()):
# 如果队列超过设定的阈值时间没有活跃,则清理队列
if current_time - timed_queue.last_active > timeout_threshold:
while not timed_queue.empty():
timed_queue.get_nowait() # 从队列中移除任务
del queue[key] # 删除队列
await asyncio.sleep(timeout_threshold.total_seconds())
功能:
clean_inactive_queues:异步任务,定期检查connection_pool中的每个TimedQueue,如果某个队列在timeout_threshold时间内没有活跃,则清理该队列中的所有连接并删除该队列。
3.4. 初始化连接池清理任务
# 维护一个连接池
connection_pool = defaultdict(TimedQueue)
clean_inactive_queues(connection_pool, timedelta(minutes=5))
问题:
- 未启动清理任务:上述代码定义了
clean_inactive_queues函数并调用它,但没有将其作为异步任务启动。应使用asyncio.create_task来启动该任务。
修正建议:
# 维护一个连接池
connection_pool = defaultdict(TimedQueue)
logging.getLogger('websockets').setLevel(logging.ERROR)
expire = 600 # redis 60s 过期
# 启动清理任务
asyncio.create_task(clean_inactive_queues(connection_pool, timedelta(minutes=5)))
4. 获取和释放 WebSocket 连接
4.1. 获取连接
async def get_connection(uri, identifier):
"""
获取WebSocket连接。如果连接池中有可用的连接,则直接返回;
否则,创建新的连接并添加到连接池。
"""
if connection_pool[identifier].empty():
# 建立新的WebSocket连接
websocket = await connect(uri)
await connection_pool[identifier].put_nowait(websocket)
# 从连接池中获取连接
websocket = await connection_pool[identifier].get_nowait()
return websocket
功能:
get_connection:根据identifier获取一个 WebSocket 连接。如果连接池中该identifier的队列为空,则新建一个连接并放入队列;否则,从队列中获取一个现有的连接。
4.2. 释放连接
async def release_connection(identifier, websocket):
"""
释放WebSocket连接,将其放回连接池。
"""
await connection_pool[identifier].put_nowait(websocket)
功能:
release_connection:将使用完毕的 WebSocket 连接放回连接池中,以便后续复用。
5. 评论回答功能
def comment_answer(message_id: int, comment: str):
with session_getter() as session:
message = session.get(ChatMessage, message_id)
if message:
message.remark = comment[:4096]
session.add(message)
session.commit()
功能:
comment_answer:为指定的消息message_id添加评论 (remark)。限制评论长度为 4096 个字符。
涉及文件:
database.models.message.ChatMessage:聊天消息的数据库模型。database.base.session_getter:数据库会话管理工具。
6. 数据流响应模型
6.1. ContentStreamResp
class ContentStreamResp(BaseModel):
role: str
content: str
功能:
ContentStreamResp:定义了内容流的响应模型,包含角色 (role) 和内容 (content) 两个字段。
6.2. ChoiceStreamResp
class ChoiceStreamResp(BaseModel):
index: int = 0
delta: ContentStreamResp = 0
session_id: str
def __str__(self) -> str:
jsonData = '{"index": "%s", "delta": %s, "session_id": "%s"}' % (
self.index, json.dumps(self.delta.dict(), ensure_ascii=False), self.session_id)
return '{"choices":[%s]}\n\n' % (jsonData)
功能:
ChoiceStreamResp:定义了选择流的响应模型,包含索引 (index)、内容增量 (delta) 和会话 ID (session_id)。__str__方法:将对象转换为特定格式的 JSON 字符串,用于流式响应。
7. 事件流生成器
async def event_stream(
webosocket: connect,
message: str,
session_id: str,
model: str,
streaming: bool,
):
payload = {'inputs': message, 'flow_id': model, 'chat_id': session_id}
try:
await webosocket.send(json.dumps(payload, ensure_ascii=False))
except Exception as e:
yield json.dumps(resp_500(message=str(e)).__dict__)
return
sync = ''
while True:
try:
msg = await webosocket.recv()
except Exception as e:
yield json.dumps(resp_500(message=str(e)).__dict__)
break
if msg is None:
continue
# 判断msg 的类型
res = json.loads(msg)
if streaming:
if res.get('type') != 'end' and res.get('message'):
delta = ContentStreamResp(role='assistant', content=res.get('message'))
yield str(ChoiceStreamResp(index=0, session_id=session_id, delta=delta))
else:
# 通过此处控制下面的close是否发送消息
if res.get('type') == 'end':
sync = res.get('message')
if res.get('type') == 'close':
if not streaming and sync:
delta = ContentStreamResp(role='assistant', content=sync)
msg = ChoiceStreamResp(index=0,
session_id=session_id,
delta=delta,
finish_reason='stop')
yield '{"choices":[%s]}' % (json.dumps(msg.dict()))
# 释放连接
elif streaming:
yield 'data: [DONE]'
await release_connection(session_id, webosocket)
break
功能:
event_stream:异步生成器,用于处理 WebSocket 连接中的事件流。它发送输入消息到 WebSocket 服务器,并逐步接收和处理响应。
工作流程:
-
发送消息:将输入消息
payload通过 WebSocket 发送到指定的uri。 -
接收响应:持续接收 WebSocket 服务器的响应消息。
-
流式响应:
- 如果
streaming为True,并且响应类型不是end且包含message,则创建ContentStreamResp并通过ChoiceStreamResp进行流式返回。
- 如果
-
非流式响应:
- 如果响应类型为
end,则记录同步消息。
- 如果响应类型为
-
-
关闭连接:
- 根据
streaming标志和是否有同步消息,生成最终的响应。 - 释放 WebSocket 连接,将其放回连接池。
- 根据
涉及文件:
resp_500:统一的 500 错误响应模型。ChatMessage:聊天消息的数据库模型。session_getter:数据库会话管理工具。
8. 总结
chat_imp.py 文件在整个聊天功能中起到了关键的作用,主要负责以下几个方面:
- 连接池管理:通过
connection_pool和TimedQueue类,管理和复用 WebSocket 连接,提高资源利用率和性能。 - WebSocket 连接处理:提供
get_connection和release_connection方法,获取和释放 WebSocket 连接。 - 事件流处理:通过
event_stream生成器,处理 WebSocket 的事件流,实现流式数据传输。 - 消息评论功能:提供
comment_answer方法,允许对消息进行评论。
9. 下一步操作
为了更好地理解和扩展这个功能,我们需要了解以下几个关键服务和模块:
bisheng.chat.manager.ChatManager:- 负责管理聊天会话,处理 WebSocket 连接和消息。
- 需要查看
bisheng/chat/manager.py(假设路径)中的实现。
- 数据库模型和 DAO:
ChatMessageDao:用于操作聊天消息的数据访问对象。session_getter:管理数据库会话的工具。
- 其他服务:
AuditLogService:记录审计日志。AssistantService和FlowService:管理助手和技能相关的业务逻辑。
建议的下一个文件:
为了继续理解聊天功能的实现,建议查看 bisheng.chat.manager 模块中的 ChatManager 类。这将帮助你了解如何管理 WebSocket 连接和处理聊天会话。
请提供 src/backend/bisheng/chat/manager.py 文件的代码,或者如果该文件在不同的位置,请提供相应的路径和代码内容。这样我们可以继续深入解析,了解 ChatManager 如何与 chat_imp.py 进行交互,并进一步理解整个聊天功能的实现。
如果 ChatManager 是在 src/backend/bisheng/chat/manager.py,请将该文件的代码复制粘贴给我,我们将继续讲解。
附加说明
在实际开发中,理解各个模块之间的交互关系至关重要。以下是一些关键点:
- 异步编程:项目大量使用异步编程(
async和await),这对于处理高并发的 WebSocket 连接非常重要。建议熟悉 Python 的asyncio库和异步编程模式。 - 数据库会话管理:使用
session_getter管理数据库会话,确保数据库操作的事务性和资源管理。 - 错误处理和日志记录:通过统一的错误响应 (
resp_500) 和日志记录 (logger),确保系统的稳定性和可维护性。 - 连接池的管理:通过连接池和定时清理任务,优化 WebSocket 连接的资源利用,避免资源泄漏。
理解这些关键点将帮助你更好地维护和扩展项目的功能。
继续学习建议
- 阅读项目文档:如果项目有详细的文档或开发指南,建议仔细阅读,以便全面了解项目的架构和设计思想。
- 逐步调试和测试:在本地环境中运行项目,使用调试工具逐步执行代码,观察各个模块的实际行为。
- 与团队沟通:向团队中经验丰富的同事请教,了解项目的最佳实践和潜在的设计模式。
- 代码阅读和分析:通过阅读和分析现有代码,理解项目的实现方式和代码风格,逐步积累项目经验。
期待你提供下一步需要解析的文件,我们将继续深入讲解!
manager.py
src/backend/bisheng/chat/manager.py
好的,让我们继续深入解析 src/backend/bisheng/chat/manager.py 文件,并了解它在整个项目中的作用和实现细节。随后,我将指导你了解实现这个功能所需的其他文件。
1. 文件概述
manager.py 文件主要负责管理聊天会话,包括 WebSocket 连接的管理、聊天历史的维护、消息的处理和分发等。它包含两个主要类:
ChatHistory:用于维护聊天历史记录。ChatManager:用于管理活跃的 WebSocket 连接、处理消息的接收与发送、管理聊天会话等。
2. 导入部分
import asyncio
import concurrent.futures
import json
import time
import uuid
from collections import defaultdict
from queue import Queue
from typing import Any, Dict, List
from uuid import UUID
from bisheng.api.services.audit_log import AuditLogService
from bisheng.api.services.user_service import UserPayload
from bisheng.api.utils import build_flow_no_yield, get_request_ip
from bisheng.api.v1.schemas import ChatMessage, ChatResponse, FileResponse
from bisheng.cache import cache_manager
from bisheng.cache.flow import InMemoryCache
from bisheng.cache.manager import Subject
from bisheng.chat.client import ChatClient
from bisheng.chat.types import IgnoreException, WorkType
from bisheng.chat.utils import process_node_data
from bisheng.database.base import session_getter
from bisheng.database.models.flow import Flow
from bisheng.database.models.message import ChatMessageDao
from bisheng.database.models.user import User, UserDao
from bisheng.graph.utils import find_next_node
from bisheng.processing.process import process_tweaks
from bisheng.utils.threadpool import ThreadPoolManager, thread_pool
from bisheng.utils.util import get_cache_key
from bisheng_langchain.input_output.output import Report
from fastapi import Request, WebSocket, WebSocketDisconnect, status
from loguru import logger
主要导入内容:
-
标准库:
asyncio、concurrent.futures:用于异步编程和并发任务处理。json、time、uuid:用于数据处理和生成唯一标识符。collections.defaultdict、queue.Queue:用于数据结构管理。typing:类型提示。
-
项目内部模块:
-
服务:
AuditLogService:用于记录审计日志。UserPayload:用户信息模型。
-
工具和辅助函数:
build_flow_no_yield、get_request_ip:用于流程构建和获取请求 IP。
-
模式:
ChatMessage、ChatResponse、FileResponse:用于定义聊天相关的数据结构。
-
缓存:
cache_manager、InMemoryCache、Subject:用于缓存管理。
-
聊天客户端:
ChatClient:聊天客户端类。
-
类型和异常:
IgnoreException、WorkType:自定义异常和工作类型。
-
数据库:
session_getter、Flow、ChatMessageDao、User、UserDao:用于数据库操作。
-
图形处理:
find_next_node:用于查找图中的下一个节点。
-
消息处理:
process_tweaks:用于处理调整。
-
线程池:
ThreadPoolManager、thread_pool:用于管理线程池。
-
实用工具:
get_cache_key:生成缓存键。
-
报告:
Report:报告生成模型。
-
FastAPI:
Request、WebSocket、WebSocketDisconnect、status:用于处理 HTTP 和 WebSocket 请求。
-
日志:
logger:用于记录日志。
-
3. ChatHistory 类
class ChatHistory(Subject):
def __init__(self):
super().__init__()
self.history: Dict[str, List[ChatMessage]] = defaultdict(list)
def add_message(
self,
client_id: str,
chat_id: str,
message: ChatMessage,
):
"""Add a message to the chat history."""
t1 = time.time()
from bisheng.database.models.message import ChatMessage
message.flow_id = client_id
message.chat_id = chat_id
if chat_id and (message.message or message.intermediate_steps
or message.files) and message.type != 'stream':
msg = message.copy()
msg.message = json.dumps(msg.message) if isinstance(msg.message, dict) else msg.message
files = json.dumps(msg.files) if msg.files else ''
msg.__dict__.pop('files')
db_message = ChatMessage(files=files, **msg.__dict__)
logger.info(f'chat={db_message} time={time.time() - t1}')
with session_getter() as seesion:
seesion.add(db_message)
seesion.commit()
seesion.refresh(db_message)
message.message_id = db_message.id
if not isinstance(message, FileResponse):
self.notify()
def empty_history(self, client_id: str, chat_id: str):
"""Empty the chat history for a client."""
self.history[get_cache_key(client_id, chat_id)] = []
功能解析:
-
继承自
Subject:ChatHistory类继承自Subject,这意味着它可以通知观察者(可能是其他组件)关于历史记录的更新。 -
属性:
self.history:使用defaultdict存储每个聊天会话的消息列表。键是生成的缓存键(基于client_id和chat_id),值是ChatMessage对象的列表。
-
方法:
-
add_message:-
功能:向聊天历史中添加一条消息。
-
流程:
-
消息预处理:将
client_id和chat_id赋值给消息对象。 -
数据库存储:
- 如果
chat_id存在且消息内容不为空且类型不是stream,则复制消息对象,序列化message和files字段(如果存在),并将其存储到数据库中。 - 更新原始消息的
message_id为数据库中生成的 ID。
- 如果
-
通知观察者:如果消息类型不是
FileResponse,则调用self.notify()以通知观察者(如需要实时更新前端)。
-
-
-
empty_history:- 功能:清空指定
client_id和chat_id的聊天历史记录。 - 流程:根据
client_id和chat_id生成的缓存键,将对应的历史记录列表重置为空列表。
- 功能:清空指定
-
4. ChatManager 类
class ChatManager:
def __init__(self):
self.active_connections: Dict[str, WebSocket] = {}
self.chat_history = ChatHistory()
self.cache_manager = cache_manager
self.cache_manager.attach(self.update)
self.in_memory_cache = InMemoryCache()
self.task_manager: List[asyncio.Task] = []
# 已连接的客户端
self.active_clients: Dict[str, ChatClient] = {}
# 记录流式输出结果
self.stream_queue: Dict[str, Queue] = {}
属性解析:
active_connections:维护一个活跃的 WebSocket 连接字典,键是生成的缓存键,值是对应的WebSocket对象。chat_history:实例化ChatHistory对象,用于管理聊天历史记录。cache_manager:引用项目中的缓存管理器,并将self.update方法附加为观察者,以便在缓存更新时接收通知。in_memory_cache:使用内存缓存(InMemoryCache)存储 LangChain 对象等临时数据。task_manager:维护一个异步任务列表,用于跟踪正在执行的任务。active_clients:维护一个活跃客户端的字典,键是客户端唯一标识符,值是ChatClient对象。stream_queue:记录流式输出结果的队列,键是生成的缓存键,值是对应的Queue对象。
5. 方法解析
5.1. update 方法
def update(self):
if self.cache_manager.current_client_id in self.active_connections:
self.last_cached_object_dict = self.cache_manager.get_last()
# Add a new ChatResponse with the data
chat_response = FileResponse(
message=None,
type='file',
data=self.last_cached_object_dict['obj'],
data_type=self.last_cached_object_dict['type'],
)
self.chat_history.add_message(self.cache_manager.current_client_id,
self.cache_manager.current_chat_id, chat_response)
功能:
- 缓存更新通知:当缓存管理器有更新时(如新数据被缓存),该方法会检查当前客户端是否在活跃连接中。
- 添加文件响应:如果是,则创建一个
FileResponse对象,并将其添加到聊天历史中。
5.2. WebSocket 连接管理方法
-
connect:async def connect(self, client_id: str, chat_id: str, websocket: WebSocket): await websocket.accept() self.active_connections[get_cache_key(client_id, chat_id)] = websocket self.stream_queue[get_cache_key(client_id, chat_id)] = Queue()功能:接受并记录新的 WebSocket 连接,初始化对应的流式输出队列。
-
reuse_connect:def reuse_connect(self, client_id: str, chat_id: str, websocket: WebSocket): self.active_connections[get_cache_key(client_id, chat_id)] = websocket self.stream_queue[get_cache_key(client_id, chat_id)] = Queue()功能:复用现有的 WebSocket 连接,重新记录连接和流式输出队列。
-
disconnect:def disconnect(self, client_id: str, chat_id: str, key: str = None): if key: logger.debug('disconnect_ws key={}', key) self.active_connections.pop(key, None) else: logger.info('disconnect_ws key={}', get_cache_key(client_id, chat_id)) self.active_connections.pop(get_cache_key(client_id, chat_id), None)功能:断开并移除指定的 WebSocket 连接。
5.3. 消息发送方法
-
send_message:async def send_message(self, client_id: str, chat_id: str, message: str): websocket = self.active_connections[get_cache_key(client_id, chat_id)] await websocket.send_text(message)功能:通过指定的 WebSocket 连接发送文本消息。
-
send_json:async def send_json(self, client_id: str, chat_id: str, message: ChatMessage, add=True): message.flow_id = client_id message.chat_id = chat_id websocket = self.active_connections[get_cache_key(client_id, chat_id)] # 增加消息记录 if add: self.chat_history.add_message(client_id, chat_id, message) await websocket.send_json(message.dict())功能:通过指定的 WebSocket 连接发送 JSON 格式的消息,并可选择是否将消息添加到聊天历史中。
5.4. 连接关闭方法
-
close_connection:async def close_connection(self, flow_id: str, chat_id: str, code: int, reason: str, key_list: List[str] = None): """close and clean ws""" if websocket := self.active_connections[get_cache_key(flow_id, chat_id)]: try: await websocket.close(code=code, reason=reason) self.disconnect(flow_id, chat_id) if key_list: for key in key_list: self.disconnect(flow_id, chat_id, key) except RuntimeError as exc: # This is to catch the following error: # Unexpected ASGI message 'websocket.close', after sending 'websocket.close' if 'after sending' in str(exc): logger.error(exc)功能:关闭并清理指定的 WebSocket 连接,并可选择性地关闭多个连接。
-
ping:async def ping(self, client_id: str, chat_id: str): ping_pong = ChatMessage( is_bot=True, message='pong', intermediate_steps='', ) await self.send_json(client_id, chat_id, ping_pong, False)功能:发送
pong消息作为心跳响应,确保连接活跃。
5.5. 缓存管理方法
-
set_cache:def set_cache(self, client_id: str, langchain_object: Any) -> bool: """ Set the cache for a client. """ self.in_memory_cache.set(client_id, langchain_object) return client_id in self.in_memory_cache功能:为指定的客户端设置缓存。
5.6. 客户端管理方法
-
accept_client:async def accept_client(self, client_key: str, chat_client: ChatClient, websocket: WebSocket): await websocket.accept() self.active_clients[client_key] = chat_client功能:接受客户端连接并记录
ChatClient对象。 -
clear_client:def clear_client(self, client_key: str): if client_key not in self.active_clients: logger.warning('close_client client_key={} not in active_clients', client_key) return logger.info('close_client client_key={}', client_key) self.active_clients.pop(client_key, None)功能:清除指定客户端的记录。
-
close_client:async def close_client(self, client_key: str, code: int, reason: str): if chat_client := self.active_clients.get(client_key): try: await chat_client.websocket.close(code=code, reason=reason) self.clear_client(client_key) except RuntimeError as exc: # This is to catch the following error: # Unexpected ASGI message 'websocket.close', after sending 'websocket.close' if 'after sending' in str(exc): logger.error(exc)功能:关闭指定客户端的 WebSocket 连接并清除其记录。
5.7. 聊天会话处理方法
-
dispatch_client:async def dispatch_client( self, request: Request, # 原始请求体 client_id: str, chat_id: str, login_user: UserPayload, work_type: WorkType, websocket: WebSocket, graph_data: dict = None): client_key = uuid.uuid4().hex chat_client = ChatClient(request, client_key, client_id, chat_id, login_user.user_id, login_user, work_type, websocket, graph_data=graph_data) await self.accept_client(client_key, chat_client, websocket) logger.debug( f'act=accept_client client_key={client_key} client_id={client_id} chat_id={chat_id}') try: while True: try: json_payload_receive = await asyncio.wait_for(websocket.receive_json(), timeout=2.0) except asyncio.TimeoutError: continue try: payload = json.loads(json_payload_receive) if json_payload_receive else {} except TypeError: payload = json_payload_receive # client内部处理自己的业务逻辑 # TODO zgq:这里可以增加线程池防止阻塞 await chat_client.handle_message(payload) except WebSocketDisconnect as e: logger.info('act=rcv_client_disconnect {}', str(e)) except IgnoreException: # client 内部自己关闭了ws链接,并无异常的情况 pass except Exception as e: # Handle any exceptions that might occur logger.exception(str(e)) await self.close_client(client_key, code=status.WS_1011_INTERNAL_ERROR, reason='后端未知错误类型') finally: try: await self.close_client(client_key, code=status.WS_1000_NORMAL_CLOSURE, reason='Client disconnected') except Exception as e: logger.exception(e) self.clear_client(client_key)功能:
- 分派客户端:为每个新的客户端连接创建一个唯一的
ChatClient对象,并接受连接。 - 消息循环:持续接收客户端发送的 JSON 消息,并将其传递给
ChatClient进行处理。 - 异常处理:处理 WebSocket 断开、忽略的异常和其他未知错误,确保连接的正确关闭和日志记录。
- 分派客户端:为每个新的客户端连接创建一个唯一的
-
handle_websocket:async def handle_websocket( self, flow_id: str, chat_id: str, websocket: WebSocket, user_id: int, gragh_data: dict = None, ): # 建立连接,并存储映射,兼容不复用ws 场景 key_list = set([get_cache_key(flow_id, chat_id)]) await self.connect(flow_id, chat_id, websocket) # autogen_pool = ThreadPoolManager(max_workers=1, thread_name_prefix='autogen') context_dict = { get_cache_key(flow_id, chat_id): { 'status': 'init', 'has_file': False, 'flow_id': flow_id, 'chat_id': chat_id } } payload = {} base_param = { 'user_id': user_id, 'flow_id': flow_id, 'chat_id': chat_id, 'type': 'end', 'category': 'system' } try: while True: try: json_payload_receive = await asyncio.wait_for(websocket.receive_json(), timeout=2.0) except asyncio.TimeoutError: json_payload_receive = '' try: payload = json.loads(json_payload_receive) if json_payload_receive else {} except TypeError: payload = json_payload_receive # websocket multi use if payload and 'flow_id' in payload: chat_id = payload.get('chat_id') flow_id = payload.get('flow_id') key = get_cache_key(flow_id, chat_id) if key not in key_list: gragh_data, message = self.preper_reuse_connection( flow_id, chat_id, websocket) context_dict.update({ key: { 'status': 'init', 'has_file': False, 'flow_id': flow_id, 'chat_id': chat_id } }) if message: logger.info('act=new_chat message={}', message) erro_resp = ChatResponse(intermediate_steps=message, **base_param) erro_resp.category = 'error' await self.send_json(flow_id, chat_id, erro_resp, add=False) continue logger.info('act=new_chat_init_success key={}', key) key_list.add(key) if not payload.get('inputs'): continue # 判断当前是否是空循环 process_param = { 'autogen_pool': thread_pool, 'user_id': user_id, 'payload': payload, 'graph_data': gragh_data, 'context_dict': context_dict } if payload: await self._process_when_payload(flow_id, chat_id, **process_param) else: for v in context_dict.values(): if v['status'] != 'init': await self._process_when_payload(v['flow_id'], v['chat_id'], **process_param) # 处理任务状态 complete_normal = await thread_pool.as_completed(key_list) complete = complete_normal # if async_task and async_task.done(): # logger.debug(f'async_task_complete result={async_task.result}') if complete: for future_key, future in complete: try: future.result() logger.debug('task_complete key={}', future_key) except Exception as e: if isinstance(e, concurrent.futures.CancelledError): continue logger.exception('feature_key={} {}', future_key, e) erro_resp = ChatResponse(**base_param) context = context_dict.get(future_key) if context.get('status') == 'init': erro_resp.intermediate_steps = f'LLM 技能执行错误. error={str(e)}' elif context.get('has_file'): erro_resp.intermediate_steps = f'文档解析失败,点击输入框上传按钮重新上传\n\n{str(e)}' else: erro_resp.intermediate_steps = f'Input data is parsed fail. error={str(e)}' context['status'] = 'init' await self.send_json(context.get('flow_id'), context.get('chat_id'), erro_resp) erro_resp.type = 'close' await self.send_json(context.get('flow_id'), context.get('chat_id'), erro_resp) except WebSocketDisconnect as e: logger.info('act=rcv_client_disconnect {}', str(e)) except Exception as e: # Handle any exceptions that might occur logger.exception(str(e)) await self.close_connection(flow_id=flow_id, chat_id=chat_id, code=status.WS_1011_INTERNAL_ERROR, reason='后端未知错误类型', key_list=key_list) finally: thread_pool.cancel_task(key_list) # 将进行中的任务进行cancel try: await self.close_connection(flow_id=flow_id, chat_id=chat_id, code=status.WS_1000_NORMAL_CLOSURE, reason='Client disconnected', key_list=key_list) except Exception as e: logger.exception(e) self.disconnect(flow_id, chat_id)功能:
-
处理 WebSocket 连接:
- 建立连接并记录。
- 持续接收和处理来自客户端的消息。
- 根据消息内容和上下文决定如何处理(如启动任务、发送响应等)。
- 处理异常和断开连接,确保资源的正确释放。
-
参数:
flow_id、chat_id:用于标识聊天会话。websocket:WebSocket 连接对象。user_id:用户 ID。graph_data:流程图数据(用于流程构建)。
-
关键流程:
-
建立连接:调用
connect方法接受并记录新的 WebSocket 连接。 -
初始化上下文:设置初始状态和上下文字典。
-
消息接收循环:
- 接收来自客户端的 JSON 消息。
- 根据消息内容和状态,调用
_process_when_payload处理消息。
-
任务管理:
- 使用线程池处理异步任务。
- 监控任务完成情况,处理结果或异常。
-
异常处理:处理 WebSocket 断开、忽略的异常和其他未知错误,确保连接的正确关闭和日志记录。
-
资源清理:在连接关闭时,取消未完成的任务并清理连接记录。
-
-
5.8. 辅助方法
-
_process_when_payload:async def _process_when_payload(self, flow_id: str, chat_id: str, autogen_pool: ThreadPoolManager, **kwargs): """ Process the incoming message and send the response. """ # set start user_id = kwargs.get('user_id') graph_data = kwargs.get('graph_data') payload = kwargs.get('payload') key = get_cache_key(flow_id, chat_id) context = kwargs.get('context_dict').get(key) status_ = context.get('status') if payload and status_ != 'init': logger.error('act=input_before_complete payload={} status={}', payload, status_) if not payload: payload = context.get('payload') context['payload'] = payload is_begin = bool(status_ == 'init' and 'action' not in payload) base_param = {'user_id': user_id, 'flow_id': flow_id, 'chat_id': chat_id} start_resp = ChatResponse(type='begin', category='system', **base_param) if is_begin: await self.send_json(flow_id, chat_id, start_resp) # 判断下是否是首次创建会话 if chat_id: res = ChatMessageDao.get_messages_by_chat_id(chat_id=chat_id) if len(res) <= 1: # 说明是新建会话 websocket = self.active_connections[key] login_user = UserPayload(**{ 'user_id': user_id, 'user_name': UserDao.get_user(user_id).user_name, }) AuditLogService.create_chat_flow(login_user, get_request_ip(websocket), flow_id) start_resp.type = 'start' # should input data step_resp = ChatResponse(type='end', category='system', **base_param) langchain_obj_key = get_cache_key(flow_id, chat_id) if status_ == 'init': has_file, graph_data = await self.preper_payload(payload, graph_data, langchain_obj_key, flow_id, chat_id, start_resp, step_resp) status_ = 'init_object' context.update({'status': status_}) context.update({'has_file': has_file}) # build in thread if not self.in_memory_cache.get(langchain_obj_key) and status_ == 'init_object': thread_pool.submit(key, self.init_langchain_object_task, flow_id, chat_id, user_id, graph_data, trace_id=chat_id) status_ = 'waiting_object' context.update({'status': status_}) # run in thread if payload and self.in_memory_cache.get(langchain_obj_key): action, over = await self.preper_action(flow_id, chat_id, langchain_obj_key, payload, start_resp, step_resp) logger.debug( f"processing_message message={payload.get('inputs')} action={action} over={over}") if not over: # task_service: 'TaskService' = get_task_service() # async_task = asyncio.create_task( # task_service.launch_task(Handler().dispatch_task, self, client_id, # chat_id, action, payload, user_id)) from bisheng_langchain.chains.autogen.auto_gen import AutoGenChain from bisheng.chat.handlers import Handler params = { 'session': self, 'client_id': flow_id, 'chat_id': chat_id, 'action': action, 'payload': payload, 'user_id': user_id, 'trace_id': chat_id } if isinstance(self.in_memory_cache.get(langchain_obj_key), AutoGenChain): # autogen chain logger.info(f'autogen_submit {langchain_obj_key}') autogen_pool.submit(key, Handler(stream_queue=self.stream_queue[key]).dispatch_task, **params) else: thread_pool.submit(key, Handler(stream_queue=self.stream_queue[key]).dispatch_task, **params) status_ = 'init' context.update({'status': status_}) context.update({'payload': {}}) # clean message功能:
- 处理传入的有效载荷:根据当前状态和传入的有效载荷,决定如何处理消息。
- 初始化响应:发送开始响应,记录审计日志(如果是新会话)。
- 准备有效载荷:调用
preper_payload方法,根据有效载荷内容决定是否需要处理文件或变量。 - 构建 LangChain 对象:如果需要,提交任务到线程池以初始化 LangChain 对象。
- 处理动作:根据有效载荷内容调用
preper_action方法,决定执行的动作(如生成报告、停止、清除历史等),并将任务提交到线程池进行处理。
-
preper_reuse_connection:def preper_reuse_connection(self, flow_id: str, chat_id: str, websocket: WebSocket): # 设置复用的映射关系 message = '' with session_getter() as session: gragh_data = session.get(Flow, flow_id) if not gragh_data: message = '该技能已被删除' if gragh_data.status != 2: message = '当前技能未上线,无法直接对话' gragh_data = gragh_data.data self.reuse_connect(flow_id, chat_id, websocket) return gragh_data, message功能:
- 准备复用连接:检查指定的
flow_id是否存在且已上线,如果不满足条件,返回相应的错误消息。 - 复用连接:调用
reuse_connect方法记录复用的 WebSocket 连接。 - 返回:返回流程图数据和可能的错误消息。
- 准备复用连接:检查指定的
-
preper_payload:async def preper_payload(self, payload, graph_data, langchain_obj_key, client_id, chat_id, start_resp: ChatResponse, step_resp: ChatResponse): has_file = False has_variable = False if 'inputs' in payload and ('data' in payload['inputs'] or 'file_path' in payload['inputs']): node_data = payload['inputs'].get('data', '') or [payload['inputs']] graph_data = self.refresh_graph_data(graph_data, node_data) # 上传文件就重新build,有点粗, 改为只有document loader 需要 node_loader = False for nod in node_data: if any('Loader' in x['id'] for x in find_next_node(graph_data, nod['id'])): node_loader = True break if node_loader: self.set_cache(langchain_obj_key, None) # rebuild object has_file = any(['InputFile' in nd.get('id', '') for nd in node_data]) has_variable = any(['VariableNode' in nd.get('id', '') for nd in node_data]) if has_file: step_resp.intermediate_steps = '文件上传完成,开始解析' await self.send_json(client_id, chat_id, start_resp) await self.send_json(client_id, chat_id, step_resp, add=False) await self.send_json(client_id, chat_id, start_resp) logger.info('input_file start_log') await asyncio.sleep(-1) # 快速的跳过 elif has_variable: await self.send_json(client_id, chat_id, start_resp) logger.info('input_variable start_log') await asyncio.sleep(-1) # 快速的跳过 return has_file, graph_data功能:
- 准备有效载荷:根据有效载荷内容,判断是否包含文件或变量输入。
- 更新图数据:调用
refresh_graph_data方法根据节点数据更新流程图数据。 - 判断节点类型:检查是否涉及文件加载器或变量节点。
- 发送响应:根据是否包含文件或变量,发送相应的响应消息。
- 返回:返回是否包含文件和更新后的流程图数据。
-
preper_action:async def preper_action(self, client_id, chat_id, langchain_obj_key, payload, start_resp: ChatResponse, step_resp: ChatResponse): langchain_obj = self.in_memory_cache.get(langchain_obj_key) batch_question = [] action = '' over = False if isinstance(langchain_obj, Report): action = 'report' step_resp.intermediate_steps = '文件解析完成,开始生成报告' await self.send_json(client_id, chat_id, step_resp) elif payload.get('action') == 'stop': action = 'stop' elif 'action' in payload: action = 'autogen' elif 'clear_history' in payload and payload['clear_history']: self.chat_history.empty_history(client_id, chat_id) action = 'clear_history' over = True elif 'data' in payload['inputs'] or 'file_path' in payload['inputs']: action = 'auto_file' batch_question = self.in_memory_cache.get(langchain_obj_key + '_question') payload['inputs']['questions'] = batch_question if not batch_question: # no question file_msg = payload['inputs'] file_msg.pop('id', '') file_msg.pop('data', '') file = ChatMessage(flow_id=client_id, chat_id=chat_id, is_bot=False, message=file_msg, type='end', user_id=step_resp.user_id) self.chat_history.add_message(client_id, chat_id, file) step_resp.message = '' step_resp.intermediate_steps = '文件解析完成' await self.send_json(client_id, chat_id, step_resp) start_resp.type = 'close' await self.send_json(client_id, chat_id, start_resp) over = True else: step_resp.intermediate_steps = '文件解析完成,开始执行' await self.send_json(client_id, chat_id, step_resp, add=False) await asyncio.sleep(-1) # 快速的跳过 return action, over功能:
- 准备动作:根据当前的 LangChain 对象和有效载荷内容,决定执行的动作类型(如生成报告、停止、清除历史、处理文件等)。
- 发送响应:根据动作类型,发送相应的中间步骤响应消息。
- 提交任务:根据动作类型,将任务提交到线程池进行处理。
- 返回:返回动作类型和是否完成的标志。
-
preper_reuse_connection:def preper_reuse_connection(self, flow_id: str, chat_id: str, websocket: WebSocket): # 设置复用的映射关系 message = '' with session_getter() as session: gragh_data = session.get(Flow, flow_id) if not gragh_data: message = '该技能已被删除' if gragh_data.status != 2: message = '当前技能未上线,无法直接对话' gragh_data = gragh_data.data self.reuse_connect(flow_id, chat_id, websocket) return gragh_data, message功能:
- 准备复用连接:检查指定的
flow_id是否存在且已上线,如果不满足条件,返回相应的错误消息。 - 复用连接:调用
reuse_connect方法记录复用的 WebSocket 连接。 - 返回:返回流程图数据和可能的错误消息。
- 准备复用连接:检查指定的
-
preper_payload和preper_action方法:这些方法用于根据有效载荷内容准备和处理动作,决定如何处理输入消息和生成相应的响应。
5.9. LangChain 对象初始化任务
-
init_langchain_object_task:async def init_langchain_object_task(self, flow_id, chat_id, user_id, graph_data): key_node = get_cache_key(flow_id, chat_id) logger.info(f'init_langchain build_begin key={key_node}') with session_getter() as session: db_user = session.get(User, user_id) # 用来支持节点判断用户权限 artifacts = {} start_time = time.time() graph = await build_flow_no_yield(graph_data=graph_data, artifacts=artifacts, process_file=True, flow_id=UUID(flow_id).hex, chat_id=chat_id, user_name=db_user.user_name) await graph.abuild() logger.info(f'init_langchain build_end timecost={time.time() - start_time}') question = [] for node in graph.vertices: if node.vertex_type in {'InputNode', 'AudioInputNode', 'FileInputNode'}: question_parse = await node.get_result() if isinstance(question_parse, list): question.extend(question_parse) else: question.append(question_parse) self.set_cache(key_node + '_question', question) input_nodes = graph.get_input_nodes() for node in input_nodes: # 只存储chain if node.base_type == 'inputOutput' and node.vertex_type != 'Report': continue self.set_cache(key_node, await node.get_result()) self.set_cache(key_node + '_artifacts', artifacts) return flow_id, chat_id功能:
- 初始化 LangChain 对象:构建和初始化流程图(LangChain 对象),处理文件上传和生成报告等。
- 更新缓存:将生成的 LangChain 对象及相关问题存储到缓存中,以供后续使用。
6. 总结
manager.py 文件在整个聊天功能中扮演了核心角色,负责以下关键功能:
- WebSocket 连接管理:
- 维护活跃的 WebSocket 连接,确保每个聊天会话有独立的连接。
- 复用和释放 WebSocket 连接,提高资源利用率。
- 聊天历史维护:
- 通过
ChatHistory类管理和记录每个聊天会话的历史消息。 - 提供添加和清空历史记录的方法。
- 通过
- 消息处理与分发:
- 接收来自客户端的消息,根据消息内容和上下文决定如何处理。
- 调用适当的服务和任务(如 LangChain 对象初始化、报告生成等),并通过线程池进行并发处理。
- 缓存管理:
- 使用
InMemoryCache和cache_manager管理临时数据和缓存对象。 - 通过观察者模式,在缓存更新时通知相应的处理方法。
- 使用
- 任务管理:
- 使用线程池(
ThreadPoolManager)处理异步任务,避免阻塞主事件循环。 - 监控任务的完成情况,处理结果或异常。
- 使用线程池(
- 异常处理与日志记录:
- 通过日志记录各种操作和异常,确保系统的可维护性和调试能力。
- 处理 WebSocket 断开、任务异常等情况,确保连接的正确关闭和资源的释放。
7. 关联的其他服务文件
为了全面理解 ChatManager 的实现,我们需要了解以下关联的服务和模块。这些模块在 manager.py 中被导入并使用:
bisheng.chat.client.ChatClient:- 职责:处理具体的聊天客户端逻辑,包括消息的接收与处理。
- 路径:
src/backend/bisheng/chat/client.py(假设路径) - 下一个步骤:提供并解析
ChatClient类的实现代码。
bisheng.api.services.audit_log.AuditLogService:- 职责:记录审计日志,跟踪用户操作。
- 路径:
src/backend/bisheng/api/services/audit_log.py - 下一个步骤:如果需要,提供并解析
audit_log.py文件。
bisheng.cache.cache_manager:- 职责:管理缓存,支持观察者模式。
- 路径:
src/backend/bisheng/cache/manager.py - 下一个步骤:提供并解析
cache_manager.py文件。
bisheng.database.models.message.ChatMessageDao:- 职责:数据访问对象,用于操作聊天消息。
- 路径:
src/backend/bisheng/database/models/message.py - 下一个步骤:提供并解析
message.py文件中的ChatMessageDao类。
bisheng.chat.handlers.Handler:- 职责:处理具体的聊天任务逻辑。
- 路径:
src/backend/bisheng/chat/handlers.py - 下一个步骤:提供并解析
handlers.py文件中的Handler类。
bisheng_langchain.chains.autogen.auto_gen.AutoGenChain:- 职责:自动生成链(AutoGenChain)的实现,用于处理自动生成的任务。
- 路径:
src/backend/bisheng_langchain/chains/autogen/auto_gen.py - 下一个步骤:提供并解析
auto_gen.py文件中的AutoGenChain类。
bisheng.cache.flow.InMemoryCache:- 职责:内存缓存实现,用于存储临时数据。
- 路径:
src/backend/bisheng/cache/flow.py - 下一个步骤:提供并解析
flow.py文件中的InMemoryCache类。
bisheng.chat.utils.process_node_data和bisheng.graph.utils.find_next_node:- 职责:处理节点数据和查找图中的下一个节点。
- 路径:
src/backend/bisheng/chat/utils.py和src/backend/bisheng/graph/utils.py - 下一个步骤:根据需要,提供并解析这些工具模块的实现代码。
8. 下一步操作
为了全面理解和扩展聊天功能,建议按以下顺序逐步提供和解析相关的服务文件:
bisheng.chat.client.ChatClient:- 了解如何处理具体的聊天客户端逻辑和消息处理。
- 请提供:
src/backend/bisheng/chat/client.py文件的代码。
bisheng.cache.manager.Subject:- 了解缓存管理器如何实现观察者模式,如何附加和通知观察者。
- 请提供:
src/backend/bisheng/cache/manager.py文件的代码。
bisheng.database.models.message.ChatMessageDao:- 了解如何进行聊天消息的数据库操作,如插入、查询、更新和删除。
- 请提供:
src/backend/bisheng/database/models/message.py文件的代码,尤其是ChatMessageDao类的实现。
bisheng.chat.handlers.Handler:- 了解如何处理具体的聊天任务逻辑,如自动生成任务、报告生成等。
- 请提供:
src/backend/bisheng/chat/handlers.py文件的代码。
bisheng_langchain.chains.autogen.auto_gen.AutoGenChain:- 了解自动生成链(AutoGenChain)的实现细节。
- 请提供:
src/backend/bisheng_langchain/chains/autogen/auto_gen.py文件的代码。
- 其他相关模块:
- 根据项目的具体实现,可能还需要查看其他模块,如缓存管理器、图形处理工具等。
示例:解析 ChatClient 类
假设你已经提供了 ChatClient 类的代码,接下来的步骤可能如下:
- 解析
ChatClient类:- 了解如何处理接收到的消息。
- 了解如何与
ChatManager交互,发送和接收消息。 - 了解如何调用其他服务(如审计日志、任务处理等)。
- 了解
AuditLogService的实现:- 了解如何记录用户的操作和系统事件。
- 了解审计日志的存储和查询方式。
- 了解缓存管理器:
- 了解缓存的存储机制、键的生成规则、缓存失效策略等。
- 了解如何通过观察者模式实现缓存更新通知。
- 了解数据库模型和 DAO:
- 了解如何定义和操作数据库模型。
- 了解
ChatMessageDao类中各个方法的实现和用途。
继续操作建议
请按照以下步骤提供下一个相关文件的代码,以便我们继续深入解析:
- 提供
ChatClient类的代码:- 路径:
src/backend/bisheng/chat/client.py - 内容:完整的
ChatClient类代码。
- 路径:
- 解析
ChatClient类:- 解释其方法和属性。
- 说明其在聊天功能中的角色和工作流程。
通过逐步解析这些关键模块,你将能够全面理解和扩展项目的聊天功能。如果在某个步骤中遇到问题或有具体的功能需求,随时告诉我,我们可以更有针对性地进行讲解和指导。
精华
好的,基于我们之前对 chat.py、chat_imp.py 和 manager.py 文件的讲解,下面我将详细介绍在这些文件基础上开发新功能的流程,包括具体的文件路径和代码实现步骤。
假设的需求示例
需求:新增一个功能,允许用户通过关键词搜索聊天记录,并返回匹配的聊天消息。
开发流程概述
-
需求分析:明确功能需求,了解需要新增的 API 端点、相关的服务逻辑和数据库操作。
-
定位项目结构:根据现有项目结构,确定在哪些文件中添加或修改代码。
-
编写代码:
- 新增 API 端点。
- 实现服务逻辑。
- 更新数据库模型(如需要)。
-
集成与测试:
- 编写单元测试。
- 本地测试与调试。
-
代码审查与提交。
-
部署与验证。
-
更新文档。
详细步骤
1. 需求分析
我们需要在现有的聊天系统中新增一个通过关键词搜索聊天记录的功能。这将涉及以下几个部分:
- 新增 API 端点:例如
/chat/search,用于接受搜索请求。 - 服务逻辑:实现搜索功能,查询数据库中的聊天记录。
- 数据库操作:确保有高效的查询机制,可能需要优化索引。
2. 定位项目结构
根据项目结构,以下是我们需要涉及的文件和目录:
- API 端点:
src/backend/bisheng/api/v1/chat.py - 服务逻辑:
src/backend/bisheng/api/services/chat_imp.py或新建一个服务文件,例如src/backend/bisheng/api/services/chat_search.py - 数据库模型:
src/backend/bisheng/database/models/message.py(假设已有ChatMessageDao类) - 测试文件:
src/backend/bisheng/test/test_chat.py(或相关测试目录) - 文档:更新 API 文档,可能是 Swagger 配置文件或项目的 README
3. 编写代码
3.1. 新增 API 端点
在 src/backend/bisheng/api/v1/chat.py 文件中,新增一个搜索聊天记录的路由。
# src/backend/bisheng/api/v1/chat.py
from fastapi import APIRouter, Depends, HTTPException
from typing import List, Optional
from bisheng.api.v1.schemas import UnifiedResponseModel, ChatMessageRead, resp_200
from bisheng.api.services.chat_search import ChatSearchService
from bisheng.api.services.user_service import UserPayload, get_login_user
router = APIRouter(tags=['Chat'])
# 其他已有的路由...
@router.get('/chat/search', response_model=UnifiedResponseModel[List[ChatMessageRead]], status_code=200)
def search_chat_messages(
keyword: str,
page: Optional[int] = 1,
page_size: Optional[int] = 20,
login_user: UserPayload = Depends(get_login_user)
):
"""
通过关键词搜索聊天记录
"""
if not keyword:
raise HTTPException(status_code=400, detail="关键词不能为空")
results, total = ChatSearchService.search_messages(
keyword=keyword,
user_id=login_user.user_id,
page=page,
page_size=page_size
)
return resp_200({
"list": results,
"total": total
})
解释:
- 路由定义:定义一个
GET请求的/chat/search端点,接受keyword、page和page_size作为查询参数。 - 依赖注入:使用
Depends(get_login_user)获取当前登录用户的信息,确保用户有权限进行搜索。 - 服务调用:调用
ChatSearchService.search_messages方法执行搜索逻辑。 - 响应:返回统一的响应模型
UnifiedResponseModel,包含搜索结果和总数。
3.2. 实现服务逻辑
创建一个新的服务文件 chat_search.py,用于处理搜索逻辑。
# src/backend/bisheng/api/services/chat_search.py
from typing import List, Tuple
from bisheng.database.models.message import ChatMessage, ChatMessageDao
from bisheng.api.v1.schemas import ChatMessageRead
from bisheng.database.base import session_getter
class ChatSearchService:
@staticmethod
def search_messages(keyword: str, user_id: int, page: int, page_size: int) -> Tuple[List[ChatMessageRead], int]:
"""
搜索用户的聊天记录,通过关键词匹配消息内容
"""
with session_getter() as session:
total = ChatMessageDao.count_search_messages(session, keyword, user_id)
messages = ChatMessageDao.search_messages(session, keyword, user_id, page, page_size)
return [ChatMessageRead.from_orm(msg) for msg in messages], total
解释:
search_messages方法:调用 DAO 层的方法进行搜索,并返回匹配的消息和总数。
3.3. 更新数据库模型
假设 ChatMessageDao 类中还没有实现搜索功能,我们需要在 src/backend/bisheng/database/models/message.py 中添加相应的方法。
# src/backend/bisheng/database/models/message.py
from typing import List, Tuple
from sqlalchemy.orm import Session
from sqlalchemy import or_
from sqlmodel import select
from bisheng.database.models.message import ChatMessage
class ChatMessageDao:
# 现有的方法...
@staticmethod
def search_messages(session: Session, keyword: str, user_id: int, page: int, page_size: int) -> List[ChatMessage]:
stmt = select(ChatMessage).where(
ChatMessage.user_id == user_id,
ChatMessage.message.ilike(f"%{keyword}%")
).offset((page - 1) * page_size).limit(page_size)
return session.exec(stmt).all()
@staticmethod
def count_search_messages(session: Session, keyword: str, user_id: int) -> int:
stmt = select(func.count()).select_from(ChatMessage).where(
ChatMessage.user_id == user_id,
ChatMessage.message.ilike(f"%{keyword}%")
)
return session.exec(stmt).one()[0]
解释:
search_messages方法:根据keyword和user_id搜索消息内容,支持分页。count_search_messages方法:计算匹配的消息总数,用于分页。
注意事项:
- 性能优化:确保数据库中的
message字段有索引,尤其是使用ilike查询时,可以考虑使用全文搜索(如 PostgreSQL 的GIN索引)以提高查询性能。
3.4. 编写测试
在 src/backend/bisheng/test/ 目录下新增测试文件 test_chat_search.py,编写单元测试以确保搜索功能正常。
# src/backend/bisheng/test/test_chat_search.py
import pytest
from bisheng.api.services.chat_search import ChatSearchService
from bisheng.database.models.message import ChatMessage
from bisheng.database.base import session_getter
@pytest.fixture
def setup_test_data():
with session_getter() as session:
# 清空现有数据
session.query(ChatMessage).delete()
# 添加测试数据
messages = [
ChatMessage(user_id=1, message="Hello World", chat_id="chat1"),
ChatMessage(user_id=1, message="Python is awesome", chat_id="chat2"),
ChatMessage(user_id=1, message="Hello Pythonistas", chat_id="chat3"),
ChatMessage(user_id=2, message="Different user message", chat_id="chat4"),
]
session.add_all(messages)
session.commit()
def test_search_chat_messages(setup_test_data):
keyword = "Python"
user_id = 1
page = 1
page_size = 10
results, total = ChatSearchService.search_messages(keyword, user_id, page, page_size)
assert total == 2
assert len(results) == 2
assert results[0].message == "Python is awesome"
assert results[1].message == "Hello Pythonistas"
def test_search_chat_messages_no_results(setup_test_data):
keyword = "Nonexistent"
user_id = 1
page = 1
page_size = 10
results, total = ChatSearchService.search_messages(keyword, user_id, page, page_size)
assert total == 0
assert len(results) == 0
def test_search_chat_messages_other_user(setup_test_data):
keyword = "Different"
user_id = 1
page = 1
page_size = 10
results, total = ChatSearchService.search_messages(keyword, user_id, page, page_size)
assert total == 0
assert len(results) == 0
解释:
-
setup_test_dataFixture:初始化测试数据,清空现有数据并插入测试消息。 -
测试用例
:
test_search_chat_messages:测试正常的搜索结果。test_search_chat_messages_no_results:测试没有匹配结果的情况。test_search_chat_messages_other_user:测试不同用户的消息不被搜索到。
3.5. 更新文档
如果项目使用 Swagger 或其他 API 文档工具,确保新增的 /chat/search 端点在文档中有所体现。通常,FastAPI 会自动根据 Pydantic 模型生成文档,但你可以在注释中添加更详细的说明。
4. 集成与测试
4.1. 本地测试与调试
-
启动本地开发环境:
确保所有依赖已安装,并启动项目。例如:
docker-compose up --build -
测试 API 端点:
使用工具如
Postman或curl发送请求,验证搜索功能是否正常工作。curl -X GET "http://127.0.0.1:8000/api/v1/chat/search?keyword=Python&page=1&page_size=10" -H "Authorization: Bearer <token>" -
调试日志:
检查日志输出,确保没有错误,并且查询逻辑按预期执行。
4.2. 运行单元测试
在项目根目录下运行测试命令,确保所有测试通过。
pytest
5. 代码审查与提交
-
创建新分支:
git checkout -b feature/chat-search -
提交代码:
git add src/backend/bisheng/api/v1/chat.py git add src/backend/bisheng/api/services/chat_search.py git add src/backend/bisheng/database/models/message.py git add src/backend/bisheng/test/test_chat_search.py git commit -m "新增通过关键词搜索聊天记录的功能" -
推送分支并创建 Pull Request (PR):
git push origin feature/chat-search在 GitHub 或其他代码托管平台上创建 PR,并描述新增功能的细节。
-
代码审查:
让团队成员进行代码审查,听取反馈并进行必要的修改。
6. 部署与验证
-
CI/CD 流程:
如果项目配置了 CI/CD 流水线,确保通过所有测试并自动部署。
-
生产环境验证:
部署后,使用实际的用户账户进行功能验证,确保搜索功能在生产环境中正常工作。
7. 文档更新
-
API 文档:
确保 Swagger 或其他 API 文档工具中包含了
/chat/search端点的说明,包括参数、响应示例等。 -
开发文档:
如果项目有开发者文档或 README,更新其中关于聊天功能的部分,添加搜索功能的使用方法和注意事项。
总结
通过上述步骤,你可以系统地在现有项目中新增一个通过关键词搜索聊天记录的功能。以下是关键的文件路径和它们的职责:
- 新增 API 端点:
src/backend/bisheng/api/v1/chat.py - 新增服务逻辑:
src/backend/bisheng/api/services/chat_search.py - 更新数据库操作:
src/backend/bisheng/database/models/message.py - 新增测试用例:
src/backend/bisheng/test/test_chat_search.py - 更新文档:根据项目的文档管理方式,更新相应的文档文件
扩展新功能的一般流程
- 确定需求:明确需要新增或修改的功能。
- 分析现有代码:了解现有项目结构和相关模块的职责。
- 定位修改点:确定需要新增或修改的文件和目录。
- 编写代码:
- 新增 API 端点。
- 实现相应的服务逻辑。
- 更新或新增数据库模型(如需要)。
- 测试:
- 编写和运行单元测试。
- 本地测试功能。
- 代码审查与合并:
- 提交代码并进行审查。
- 根据反馈进行修改。
- 部署:
- 通过 CI/CD 流程部署新功能。
- 在生产环境中验证功能。
- 更新文档:确保新增功能在文档中有详细说明。
建议
- 保持模块化:尽量将新功能的逻辑封装在独立的服务或模块中,避免与现有功能紧密耦合。
- 遵循编码规范:确保代码符合项目的编码风格和规范,保持代码的一致性和可读性。
- 编写充分的测试:确保新增功能有充分的单元测试和集成测试,保障功能的稳定性。
- 与团队沟通:在开发过程中,定期与团队成员沟通,确保开发方向与项目目标一致。

被折叠的 条评论
为什么被折叠?



