FEATURED · 精选文章

基于大语言模型与向量检索的角色扮演对话系统构建实战

发布时间 / 2026/9/3 6:39:18
来源 / 创域科博编辑部
栏目 / 资讯中心
基于大语言模型与向量检索的角色扮演对话系统构建实战 最近在整理个人项目时遇到一个挺有意思的需求如何将一套自定义的、带有强烈个人风格甚至有点“中二”的角色设定俗称“私设”与一个智能问答ASK系统进行深度结合并实现二次迭代升级。这不仅仅是简单的问答匹配更涉及到角色性格模拟、上下文记忆、风格化文本生成等多个技术环节。网上关于通用聊天机器人的教程很多但针对这种高度定制化、带有特定人设的AI对话系统成体系的实战资料却比较零散。本文将从一个全栈开发者的视角系统性地拆解如何从零构建一个“超中二私设”的ASK系统V2.0。我们将覆盖从需求分析、技术选型、核心模块实现包括人格嵌入、记忆管理、对话生成到最终部署上线的完整闭环。文章会提供可直接复用的代码模块、详细的配置说明以及开发过程中必踩的“坑”及其解决方案。无论你是想为自己创作的角色打造一个“灵魂”还是希望深入学习对话式AI的定制化开发这篇文章都能提供一条清晰的实践路径。1. 项目背景与核心概念解析在开始敲代码之前我们首先要明确几个关键概念并厘清这个项目的核心目标。1.1 什么是“私设”与“ASK系统”私设 (Private Setting): 源于同人创作圈指个人对现有作品角色或原创角色的一套独立于官方设定的详细背景、性格、经历等设定。在本项目中“超中二私设”特指那些性格夸张、用语独特常包含自创词汇、华丽修辞、行为模式固定的角色设定。ASK系统: 一个允许用户向特定角色提问并由该角色基于其设定进行回答的交互系统。其核心是让AI模拟特定角色的思维和语言模式进行对话。1.2 V2.0 系统的核心目标相较于简单的关键词匹配回复的V1.0本次迭代ASK②旨在实现以下进阶目标人格一致性: 对话需严格遵循“私设”中定义的性格、口癖、知识背景和价值观避免生成OOCOut Of Character角色性格偏离的回复。上下文记忆: 系统需要具备短期对话记忆和长期设定记忆能力能记住在同一会话中聊过的话题并能基于角色的“过往经历”设定进行回应。风格化文本生成: 回复文本需要体现“中二”风格如使用特定句式、隐喻、夸张的自我称谓等而非普通的白话文。可扩展的设定管理: 角色的“私设”应该以结构化的方式管理便于增删改查并能被系统核心模块方便地调用。1.3 技术挑战如何将非结构化的文本设定转化为机器可理解的结构如何在生成回复时优先考虑角色设定而非通用知识如何量化并控制生成文本的“中二度”如何设计一个轻量且高效的记忆模块2. 技术选型与环境准备基于上述目标我们选择以下技术栈力求在效果、复杂度和资源消耗间取得平衡。2.1 技术栈说明后端框架: Python FastAPI。FastAPI异步特性好适合处理对话请求且能自动生成API文档。核心AI模型: 考虑到私有化部署和定制化需求我们选用ChatGLM3-6B或Qwen1.5-7B这类优秀的开源中英双语大语言模型作为基座。它们比纯云端API更具可控性方便我们进行提示词工程和轻量微调。嵌入与向量数据库: 使用sentence-transformers生成设定的文本嵌入并存入ChromaDB或FAISS这类轻量级向量数据库用于实现设定知识的快速检索。记忆管理: 使用LangChain框架的ConversationBufferWindowMemory或自定义记忆体来管理对话上下文其集成度高能简化开发。前端可选: 一个简单的HTML/JS页面用于交互或直接使用API测试工具如Postman。2.2 开发环境与版本以下是本文示例所使用的主要环境与版本实际操作时请根据你的系统进行调整。# 环境概述 操作系统: Ubuntu 22.04 LTS / Windows 11 WSL2 Python版本: 3.10 CUDA版本: 11.8 (如需GPU加速) 内存: 建议16GB以上 显存: 如需本地运行6B/7B模型建议8GB以上 # 核心库及版本示例请以实际最新稳定版为准 fastapi0.104.1 uvicorn[standard]0.24.0 torch2.1.0 transformers4.35.0 sentence-transformers2.2.2 chromadb0.4.18 langchain0.0.340 langchain-community0.0.10 accelerate0.25.0 # 用于模型加载优化2.3 项目结构初始化创建一个清晰的项目目录是成功的第一步。super_chuunibyou_ask_v2/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 应用入口 │ ├── config.py # 配置文件 │ ├── models.py # 数据模型Pydantic │ ├── character/ # 角色设定核心模块 │ │ ├── __init__.py │ │ ├── manager.py # 角色设定管理器 │ │ └── profile.py # 角色设定数据结构 │ ├── memory/ # 记忆模块 │ │ ├── __init__.py │ │ └── manager.py # 对话记忆管理器 │ ├── core/ # 核心生成模块 │ │ ├── __init__.py │ │ ├── llm_client.py # 大模型客户端封装 │ │ └── prompt_engineer.py # 提示词工程师 │ ├── knowledge/ # 知识库向量数据库 │ │ ├── __init__.py │ │ └── vector_store.py │ └── api/ # 路由层 │ ├── __init__.py │ └── endpoints.py # API端点 ├── data/ │ ├── characters/ # 存放角色设定文件JSON/YAML │ └── chroma_db/ # 向量数据库持久化目录 ├── requirements.txt └── README.md使用以下命令安装基础依赖pip install fastapi uvicorn torch transformers3. 核心模块设计与实现接下来我们逐一实现系统的核心模块。3.1 角色设定管理模块 (character/)这是系统的灵魂。我们需要将角色的“中二私设”结构化。app/character/profile.py: 定义角色设定的数据结构。from pydantic import BaseModel, Field from typing import List, Optional, Dict class CharacterProfile(BaseModel): 角色设定档案 id: str Field(..., description角色唯一标识) name: str Field(..., description角色名称) # 核心性格描述用于生成固定口吻 personality: str Field(..., description核心性格描述如傲娇、自负、内心温柔) # 关键背景设定用于知识检索 background: str Field(..., description背景故事、重要经历) # 语言风格示例用于few-shot学习 speech_style: List[str] Field(default_factorylist, description典型对话例句列表) # 自定义词汇表中二词汇、技能名等 glossary: Dict[str, str] Field(default_factorydict, description自定义词汇解释) # 其他元数据 tags: List[str] Field(default_factorylist, description标签如魔法少女,吸血鬼,未来科技) def to_context_text(self) - str: 将档案转换为可插入提示词的文本 context_lines [ f角色名称{self.name}, f性格{self.personality}, f背景{self.background}, 语言风格示例, ] context_lines.extend([f- {example} for example in self.speech_style[:3]]) # 取前3个示例 if self.glossary: context_lines.append(自定义词汇) context_lines.extend([f- {k}: {v} for k, v in list(self.glossary.items())[:5]]) return \n.join(context_lines)app/character/manager.py: 管理多个角色档案的加载与获取。import json import yaml from pathlib import Path from typing import Dict from .profile import CharacterProfile class CharacterManager: def __init__(self, data_dir: Path): self.data_dir data_dir self.characters: Dict[str, CharacterProfile] {} self._load_all_characters() def _load_all_characters(self): for file_path in self.data_dir.glob(*.json): with open(file_path, r, encodingutf-8) as f: data json.load(f) profile CharacterProfile(**data) self.characters[profile.id] profile # 也可以支持YAML格式 for file_path in self.data_dir.glob(*.yaml) | self.data_dir.glob(*.yml): with open(file_path, r, encodingutf-8) as f: data yaml.safe_load(f) profile CharacterProfile(**data) self.characters[profile.id] profile def get_character(self, character_id: str) - Optional[CharacterProfile]: return self.characters.get(character_id) def list_characters(self) - List[CharacterProfile]: return list(self.characters.values())3.2 知识库与向量检索模块 (knowledge/)为了让模型能精准引用角色设定我们将设定的关键文本向量化存储。app/knowledge/vector_store.py: 负责处理设定的嵌入与检索。from sentence_transformers import SentenceTransformer import chromadb from chromadb.config import Settings from typing import List, Dict, Any import logging logger logging.getLogger(__name__) class CharacterKnowledgeStore: def __init__(self, persist_directory: str ./data/chroma_db, embedding_model_name: str paraphrase-multilingual-MiniLM-L12-v2): # 使用轻量级的多语言句子嵌入模型 self.embedding_model SentenceTransformer(embedding_model_name) # 初始化Chroma客户端持久化存储 self.client chromadb.PersistentClient( pathpersist_directory, settingsSettings(anonymized_telemetryFalse) ) # 创建或获取一个集合类似数据库的表 self.collection self.client.get_or_create_collection(namecharacter_profiles) def add_profile(self, profile_id: str, profile_text: str, metadata: Dict[str, Any]): 将角色设定的文本添加到向量库 # 生成嵌入向量 embedding self.embedding_model.encode(profile_text).tolist() # 存入集合 self.collection.add( embeddings[embedding], documents[profile_text], # 存储原始文本用于返回 metadatas[metadata], # 存储角色ID等元数据 ids[profile_id] ) logger.info(fAdded profile for character: {profile_id}) def search_similar(self, query: str, n_results: int 3) - List[Dict]: 检索与查询最相关的角色设定片段 query_embedding self.embedding_model.encode(query).tolist() results self.collection.query( query_embeddings[query_embedding], n_resultsn_results ) # 组织返回结果 retrieved_info [] if results[documents]: for doc, meta in zip(results[documents][0], results[metadatas][0]): retrieved_info.append({ content: doc, character_id: meta.get(character_id, unknown) }) return retrieved_info3.3 记忆管理模块 (memory/)我们使用LangChain来管理对话历史它提供了便捷的上下文窗口管理。app/memory/manager.py:from langchain.memory import ConversationBufferWindowMemory from langchain.schema import BaseMessage, HumanMessage, AIMessage from typing import List, Dict class DialogueMemoryManager: def __init__(self, k: int 10): Args: k: 保留最近几轮对话的记忆窗口大小 # 使用LangChain的对话缓冲记忆只保留最近k轮 self.memory ConversationBufferWindowMemory(kk, return_messagesTrue) # 可在此扩展长期记忆如将重要信息存入向量库 def save_context(self, user_input: str, ai_response: str): 保存一轮对话的上下文 self.memory.save_context({input: user_input}, {output: ai_response}) def load_memory_as_text(self) - str: 将记忆转换为文本用于构造提示词 messages self.memory.chat_memory.messages if not messages: return # 将LangChain的Message对象转换为简单文本 history_text for msg in messages[-self.memory.k*2:]: # 确保不超过窗口 if isinstance(msg, HumanMessage): history_text fUser: {msg.content}\n elif isinstance(msg, AIMessage): history_text fAssistant: {msg.content}\n return history_text def clear(self): 清空当前对话记忆 self.memory.clear()3.4 大模型客户端与提示词工程 (core/)这是连接所有模块并生成最终回复的大脑。app/core/llm_client.py: 封装本地大模型的调用。from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline import torch from app.config import settings import logging logger logging.getLogger(__name__) class LocalLLMClient: def __init__(self, model_path: str, device: str None): self.device device or (cuda if torch.cuda.is_available() else cpu) logger.info(fLoading model from {model_path} on {self.device}...) # 加载tokenizer和模型 self.tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) self.model AutoModelForCausalLM.from_pretrained( model_path, torch_dtypetorch.float16 if self.device cuda else torch.float32, low_cpu_mem_usageTrue, trust_remote_codeTrue ).to(self.device) # 构建文本生成管道 self.generator pipeline( text-generation, modelself.model, tokenizerself.tokenizer, device0 if self.device cuda else -1, ) logger.info(Model loaded successfully.) def generate(self, prompt: str, max_new_tokens: int 512, temperature: float 0.7, top_p: float 0.9) - str: 生成回复 try: outputs self.generator( prompt, max_new_tokensmax_new_tokens, do_sampleTrue, temperaturetemperature, top_ptop_p, pad_token_idself.tokenizer.eos_token_id, repetition_penalty1.1, # 轻微重复惩罚使内容更丰富 ) generated_text outputs[0][generated_text] # 移除提示词部分只返回新生成的内容 response generated_text[len(prompt):].strip() return response except Exception as e: logger.error(fError during generation: {e}) return 思考中出现了紊乱...app/core/prompt_engineer.py: 这是最关键的部分负责构造能让模型“扮演”角色的提示词。class PromptEngineer: staticmethod def build_chuunibyou_prompt( character_context: str, dialogue_history: str, user_query: str, retrieved_knowledge: List[Dict] None ) - str: 构建中二风格角色扮演提示词。 提示词的结构和措辞直接影响生成效果。 # 1. 系统指令定义角色和基本规则 system_instruction f你是一个沉浸式角色扮演AI必须严格遵循以下设定进行回应。 【角色设定】 {character_context} 【核心规则】 1. **绝对人格一致**你的所有回应必须完全符合上述角色设定包括性格、口癖、背景知识。 2. **中二风格强化**使用富有戏剧性、夸张、带有一点幻想色彩的词汇和句式。可以适当使用自创的招式名、称号和隐喻。 3. **基于记忆对话**参考之前的对话历史保持话题连贯性。 4. **知识引用**如果设定中有相关信息优先使用设定中的知识回答。 # 2. 整合检索到的相关知识增强设定感知 knowledge_section if retrieved_knowledge and len(retrieved_knowledge) 0: knowledge_text \n.join([f- {item[content][:200]}... for item in retrieved_knowledge[:2]]) # 取前两条避免过长 knowledge_section f\n【相关设定记忆】\n{knowledge_text}\n # 3. 整合对话历史 history_section f\n【对话历史】\n{dialogue_history} if dialogue_history else # 4. 当前查询 query_section f\n\n现在用户对你说道“{user_query}”\n请以{character_context.split(角色名称)[1].split(\\n)[0]}的身份和风格进行回应 # 5. 最终组装 full_prompt system_instruction knowledge_section history_section query_section return full_prompt4. 服务集成与API实现将上述模块在FastAPI应用中组装起来。app/main.py: 应用主入口。from fastapi import FastAPI, HTTPException from app.api.endpoints import router as api_router from app.character.manager import CharacterManager from app.knowledge.vector_store import CharacterKnowledgeStore from app.core.llm_client import LocalLLMClient import logging from pathlib import Path logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) app FastAPI(title超中二私设ASK系统 V2.0, description一个高度定制化的角色对话AI系统) # 全局初始化核心组件实际生产环境应考虑依赖注入 CHARACTER_DATA_DIR Path(./data/characters) KNOWLEDGE_STORE_DIR Path(./data/chroma_db) MODEL_PATH ./models/chatglm3-6b # 替换为你的实际模型路径 character_manager None knowledge_store None llm_client None app.on_event(startup) async def startup_event(): global character_manager, knowledge_store, llm_client logger.info(Initializing system components...) character_manager CharacterManager(CHARACTER_DATA_DIR) knowledge_store CharacterKnowledgeStore(persist_directorystr(KNOWLEDGE_STORE_DIR)) # 初始化向量库遍历所有角色将其设定文本添加到知识库 for character in character_manager.list_characters(): profile_text character.to_context_text() knowledge_store.add_profile( profile_idcharacter.id, profile_textprofile_text, metadata{character_id: character.id, name: character.name} ) llm_client LocalLLMClient(model_pathMODEL_PATH) logger.info(All components initialized.) app.include_router(api_router, prefix/api/v1)app/api/endpoints.py: 定义核心对话API。from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from typing import Optional import logging from app.memory.manager import DialogueMemoryManager from app.core.prompt_engineer import PromptEngineer # 假设通过某种方式获取全局组件实际可使用FastAPI的Depends from app.main import character_manager, knowledge_store, llm_client logger logging.getLogger(__name__) router APIRouter() # 内存会话存储简单示例生产环境应用数据库或Redis session_memories {} class AskRequest(BaseModel): character_id: str message: str session_id: Optional[str] default_session # 用于区分不同用户的对话 class AskResponse(BaseModel): reply: str session_id: str router.post(/ask, response_modelAskResponse) async def ask_character(request: AskRequest): 向指定角色提问 # 1. 获取角色设定 character character_manager.get_character(request.character_id) if not character: raise HTTPException(status_code404, detailCharacter not found) # 2. 获取或创建该会话的记忆体 memory_key f{request.session_id}_{request.character_id} if memory_key not in session_memories: session_memories[memory_key] DialogueMemoryManager(k8) memory session_memories[memory_key] # 3. 基于用户问题检索相关角色设定知识 retrieved_knowledge knowledge_store.search_similar(request.message, n_results2) # 4. 构建提示词 prompt PromptEngineer.build_chuunibyou_prompt( character_contextcharacter.to_context_text(), dialogue_historymemory.load_memory_as_text(), user_queryrequest.message, retrieved_knowledgeretrieved_knowledge ) logger.debug(fGenerated prompt length: {len(prompt)}) # 5. 调用大模型生成回复 reply llm_client.generate(prompt, max_new_tokens256, temperature0.85) # 温度稍高增加创造性 # 6. 保存本轮对话到记忆 memory.save_context(request.message, reply) # 7. 返回结果 return AskResponse(replyreply, session_idrequest.session_id) router.post(/session/clear) async def clear_session(session_id: str, character_id: str): 清空指定会话的记忆 memory_key f{session_id}_{character_id} if memory_key in session_memories: session_memories[memory_key].clear() return {message: Session memory cleared.} return {message: Session not found.}5. 运行与测试5.1 准备角色设定文件在data/characters/下创建一个JSON文件例如dark_flame_master.json:{ id: dfm_001, name: 暗炎之主, personality: 表面冷酷寡言实则内心戏丰富深信自己体内封印着灭世之炎习惯用晦涩的隐喻和古老的预言句式说话对甜食毫无抵抗力但绝不承认。, background: 诞生于虚空裂缝曾是上古炎魔军的统帅因一场背叛而被封印力量流落至现世以高中生身份隐藏。右眼佩戴着抑制力量的封印眼罩。, speech_style: [ “凡人你触及的不过是真实之海的涟漪。”, “哼这份甜腻...勉强配得上作为吾之力量的祭品。”, “封印正在松动...吾之左臂感到一阵刺痛。”其实只是趴着睡觉压麻了 ], glossary: { 虚空之触: 其能力之一可感知周围能量流动, 炎魔契约: 其力量的来源也是痛苦的根源, 封印眼罩: 抑制其毁灭性力量的器具摘下需付出代价 }, tags: [中二, 傲娇, 魔法, 转生] }5.2 启动服务# 安装所有依赖 pip install -r requirements.txt # 启动FastAPI服务 uvicorn app.main:app --reload --host 0.0.0.0 --port 80005.3 测试API使用curl或 Postman 进行测试curl -X POST http://localhost:8000/api/v1/ask \ -H Content-Type: application/json \ -d { character_id: dfm_001, message: 你今天数学考得怎么样, session_id: test_user_1 }预期风格的回复示例“数学呵那不过是凡界规则的拙劣模仿。吾之目光所及乃是星辰运行的法则与混沌的微分方程。那份试卷...早已被吾之‘虚空之触’解析殆尽。瞥了一眼角落的59分卷子...只是不屑于填写那些拘泥形式的答案罢了。”6. 常见问题与排查思路在开发和运行过程中你可能会遇到以下问题问题现象可能原因排查与解决思路模型加载失败提示显存不足1. 模型过大。2. 未使用torch.float16量化。3. 后台有其他进程占用显存。1. 尝试使用load_in_8bit或load_in_4bit需安装bitsandbytes进行量化加载。2. 换用更小模型如Qwen1.5-1.8B。3. 使用nvidia-smi查看并关闭无关进程。生成回复完全不符合角色设定OOC1. 提示词Prompt不够强。2. 角色设定文本太短或模糊。3. 模型温度temperature参数过高随机性太强。1. 强化提示词中的“绝对人格一致”规则在speech_style中提供更典型的例句。2. 丰富personality和background字段添加具体行为和事件。3. 将temperature调低至0.5-0.8降低随机性。回复内容重复或陷入循环1. 重复惩罚repetition_penalty过低。2. 对话历史过长模型陷入局部模式。1. 适当提高repetition_penalty如1.2。2. 减少记忆窗口k的大小或定期清除无关历史。向量检索返回无关内容1. 嵌入模型不适用于中文或特定领域。2. 角色设定文本分块不合理。1. 尝试更换嵌入模型如text2vec系列或m3e。2. 将长背景拆分成多个短句或片段分别存入向量库。API响应速度慢1. 模型首次生成慢。2. 向量检索未建立索引。1. 首次生成后会有加速考虑使用GPU。2. 确保ChromaDB使用的是持久化客户端避免每次重建索引。对话历史混乱1.session_id管理错误不同用户对话混在一起。1. 检查session_id的生成和传递逻辑确保唯一性。2. 将session_memories替换为Redis等外部存储。7. 最佳实践与进阶优化完成基础版本后可以考虑以下方向进行深度优化打造更强大的系统。7.1 提示词工程优化Few-Shot CoT链式思考在提示词中不仅给例句还可以模拟角色的“内心独白”引导模型先思考再说话。【思考过程仅内部】用户问数学考试...作为暗炎之主应该把数学概念扭曲成魔法理论...上次设定里提到“星辰运行的法则”可以用...不能承认考得差要归结于不屑... 【最终回复】“数学呵那不过是...”风格权重控制在提示词中明确风格强度。“请以**极度中二等级9/10**的风格回应”。7.2 记忆系统增强长期记忆向量库不仅存储静态设定还将对话中重要的用户信息如“用户喜欢猫”或角色自述的新“设定”如“吾昨日与影魔大战三百回合”摘要后存入向量库实现跨会话记忆。记忆摘要当对话轮数过多时使用LLM对过往长对话进行摘要用摘要替代原始历史节省Token并聚焦核心信息。7.3 性能与部署模型量化与加速使用GPTQ,AWQ或vLLM对模型进行4bit/8bit量化大幅提升推理速度并降低显存消耗。API异步化确保生成请求是异步的async/await避免阻塞FastAPI事件循环。配置热更新角色设定文件修改后无需重启服务通过API端点触发CharacterManager和KnowledgeStore的重新加载。7.4 安全与伦理内容过滤器在最终回复输出前加入一层轻量级的内容安全过滤防止模型在角色扮演下生成有害或不适当内容。用户知情在交互界面明确告知用户正在与AI角色对话。设定边界在角色设定中预先定义对话边界例如“该角色不会提供医疗、金融建议”。构建一个“有灵魂”的角色对话系统技术实现只是骨架真正的血肉在于对角色设定的深度理解和精巧的提示词设计。本文提供的架构是一个坚实的起点你可以通过不断迭代角色档案、优化提示词模板、增强记忆逻辑让你笔下的“暗炎之主”或任何“中二”角色真正在数字世界中生动起来。下一步可以尝试为系统增加语音合成、情感分析调整回复语气甚至简单的剧情推进功能让互动体验更加沉浸。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻