FEATURED · 精选文章

Opik SimulatedUser 实战指南:用 LLM 驱动的人物角色模拟器做多轮对话测试

发布时间 / 2026/9/13 18:08:46
来源 / 创域科博编辑部
栏目 / 资讯中心
Opik SimulatedUser 实战指南:用 LLM 驱动的人物角色模拟器做多轮对话测试 Opik SimulatedUser 实战指南用 LLM 驱动的人物角色模拟器做多轮对话测试【免费下载链接】comet-llmDebug, evaluate, and monitor your LLM applications, RAG systems, and agentic workflows with comprehensive tracing, automated evaluations, and production-ready dashboards.项目地址: https://gitcode.com/GitHub_Trending/co/comet-llm导读SimulatedUser是 Opik Python SDK 中用于多轮对话仿真multi-turn conversation simulation的核心类它既可以通过任意受支持的 LLM 模型生成贴合上下文与人物设定的用户回复也可以使用预设的固定回复实现确定性测试。本文将基于官方 API 文档结合仓库源码逐层拆解其构造函数、generate_response方法、模型解析机制以及与run_simulation的端到端集成帮助你快速构建面向客服、产品答疑等场景的自动化对话测试。一、SimulatedUser 是什么在对话类应用客服机器人、RAG 问答助手、Agent 工作流的测试中人工扮演用户进行多轮对话既耗时又难以覆盖不同人群。SimulatedUser提供了一种自动化方案它模拟用户一侧的行为把对话历史交给它由它生成下一条用户消息。根据 simulated_user.py 的类注释该类的定位是A simulated user that generates responses using LLMs or fixed responses. The user simulator generates string responses that are then incorporated into the conversation by the application logic.即它只负责产出字符串形式的用户消息至于如何把这些消息拼进完整对话由上层应用如run_simulation或你自己的调用逻辑负责。这一设计让 SimulatedUser 与具体业务解耦可被灵活嵌入多种测试框架。其核心能力包括LLM 驱动的响应使用任意受 Opik 模型工厂支持的 LLM 模型生成带上下文的用户回复固定回复模式提供预设回复列表时按顺序循环使用用于确定性测试人物设定Persona通过 persona 描述用户性格与行为作为 system prompt 引导生成对话上下文感知基于完整对话历史生成回复保持多轮行为连贯。在 Opik 中的导入方式为from opik.simulation import SimulatedUser该模块同时导出run_simulation见 simulation/init.py。二、快速上手安装与第一个模拟用户SimulatedUser属于 Opik Python SDK 的一部分随opik包一起分发无需额外安装独立依赖。但若使用 LLM 生成模式需要确保对应模型提供方的依赖可用Opik 模型工厂内部基于 LiteLLMAnthropic 模型可选用原生 SDK。最小可用示例from opik.simulation import SimulatedUser # 创建一个愤怒的顾客人物 user_simulator SimulatedUser( personaYou are a frustrated customer who wants a refund for a broken product, modelopenai/gpt-5-nano ) # 基于对话历史生成一条用户回复 conversation [ {role: assistant, content: Hello, how can I help you today?}, {role: user, content: My product broke after 2 days, I want a refund.}, {role: assistant, content: Im sorry to hear that. What happened?} ] response user_simulator.generate_response(conversation) print(response) # 可能的输出It just stopped working! Ive barely used it...在__init__中即使你随后改用固定回复SDK 也会调用模型工厂创建模型实例见源码第 40 行self._llm get_model(model_nameself.model)因此传入的model名必须是模型工厂可解析的合法名称。三、构造函数与参数详解官方文档给出的构造函数签名如下SimulatedUser( persona: str, model: Optional[str] None, fixed_responses: Optional[List[str]] None )三个参数的实际语义与 simulated_user.py 的实现一一对应参数类型默认值说明personastr必填用户性格与行为的描述文本被拼接进 system prompt指导 LLM 生成符合该人设的回复modelstrNone用于生成回复的 LLM 模型名省略时由get_default_model_name()解析默认模型fixed_responsesList[str]None预设回复列表提供后generate_response将按顺序循环使用不再调用 LLMpersona用户人格设定persona不是简单标签而是一段完整的自然语言描述最终以 system prompt 形式注入。从源码第 66-70 行可以看到完整的提示词模板You are a simulated user with the following persona: {self.persona} Your task is to generate realistic user messages that this persona would send in a conversation. Respond as if you are the user, not as an assistant describing the user. Generate a single user message that fits your persona and the conversation context.值得注意的两点设计其一明确要求以用户身份说话而不是像助手一样描述用户其二每次只生成一条用户消息。写得越具体身份、情绪、诉求、语气生成的回复越稳定。model模型选择与默认值解析model省略时SimulatedUser会调用models_factory.get_default_model_name()最终读取OpikConfig().default_llm。在 config.py 中该配置项的默认值为openai/gpt-5-nano并支持通过环境变量OPIK_DEFAULT_LLM覆盖——这与文档中省略时默认使用OPIK_DEFAULT_LLM未设置时为openai/gpt-5-nano的描述一致。模型实例的创建统一走 models_factory.py 的get()工厂函数Anthropic 系列模型且环境中存在anthropicSDK 时使用原生AnthropicChatModel其余模型使用LiteLLMChatModel。工厂内部带有实例缓存_MODEL_CACHE以模型名、是否追踪、额外 kwargs 为键因此多次创建相同配置的模拟用户不会重复初始化模型后端。fixed_responses确定性回复列表fixed_responses提供后generate_response会以内部计数器_response_index对列表长度取模实现顺序循环、耗尽后从头再来源码第 53-58 行。这一机制在单元测试 test_simulated_user.py 中有完整覆盖三次调用依次返回Response 1/2/3第四次调用回到Response 1。四、核心方法 generate_response 深度解析官方文档给出的方法签名generate_response(conversation_history: List[Dict[str, str]]) - str参数conversation_history为消息字典列表每个字典必须包含role与content两个键role 取值通常为system、user、assistant。返回str即模拟用户产出的单条回复文本。注意返回的是字符串而非消息字典——把role: user包装回去的工作由上层调用方完成。行为分支对应源码第 42-61 行若fixed_responses非空直接按顺序循环取出固定回复并返回完全绕过 LLM否则调用_generate_llm_response()用 LLM 基于 persona 与对话历史生成回复LLM 调用抛出任何异常时返回兜底文案Im having trouble responding right now. ({错误信息})保证模拟流程不因单次模型故障中断。对话历史如何送入 LLM_generate_llm_response的内部流程源码第 63-87 行分为三步拼装消息列表构造[{role: system, content: persona提示词}]再把conversation_history原样追加到其后文本化转换调用_format_messages_as_text()把消息字典序列化为带角色前缀的纯文本——system→System:、user→User:、assistant→Assistant:未知角色用role.title()前缀各消息以换行拼接生成调用self._llm.generate_string(inputconversation_text)得到字符串回复。单元测试 test_simulated_user.py 验证了这一转换15 条消息的历史中User: Message 0与Assistant: Response 14都出现在最终输入里。关于历史截断需要留意的事实官方文档提到会自动将对话历史限制为最近 10 条消息以避免超出 token 限制。但从当前仓库源码看_format_messages_as_text并未对历史做截断conversation_history会被完整送入 LLM对应的长历史单元测试也断言从Message 0到Message 14全部包含在输入中。因此在实际使用中若对话轮次很长建议在调用侧自行控制传入历史长度不要依赖自动截断。五、固定回复模式确定性测试的正确姿势当测试需要可复现、不依赖外部模型时使用fixed_responsesfrom opik.simulation import SimulatedUser # 用预设回复做确定性测试 user_simulator SimulatedUser( personaTest user, fixed_responses[ I want a refund, This is taking too long, Can I speak to a manager?, Im not satisfied with this service ] ) # 回复按列表顺序循环 response1 user_simulator.generate_response([]) # I want a refund response2 user_simulator.generate_response([]) # This is taking too long response3 user_simulator.generate_response([]) # Can I speak to a manager?注意在此模式下conversation_history参数会被忽略源码直接短路返回适合构造完全确定的脚本化对话同时应意识到__init__仍会初始化 LLM 后端self._llm get_model(...)如需彻底离线运行需确保模型工厂初始化不会触发网络请求模型实例创建本身通常不发起调用。六、多 Persona 场景模拟不同类型的用户为覆盖更多真实用户行为可以为同一测试创建多个SimulatedUser实例from opik.simulation import SimulatedUser # 满意的顾客 happy_customer SimulatedUser( personaYou are a satisfied customer who loves the product and wants to buy more, modelopenai/gpt-5-nano ) # 困惑的新手用户 confused_user SimulatedUser( personaYou are a confused user who needs help understanding how to use the product, modelopenai/gpt-5-nano ) # 技术型用户 technical_user SimulatedUser( personaYou are a technical user who asks detailed questions about implementation and integration, modelopenai/gpt-5-nano )每个实例持有独立的 persona 与可选的独立模型可以并行用于同一被测应用横向对比应用对不同用户类型的表现。七、与 run_simulation 集成完整的端到端对话仿真SimulatedUser通常与run_simulation配合使用。run_simulation定义在 simulator.py其职责是驱动模拟用户 ↔ 被测应用的多轮对话循环并把整段对话以 thread 形式写入 Opik 便于评估。run_simulation 签名与参数run_simulation( app: Callable, # 处理消息的应用函数 user_simulator: SimulatedUser, # 模拟用户实例 initial_message: Optional[str] None, # 首条用户消息缺省由模拟器生成 max_turns: int 5, # 最大对话轮数 thread_id: Optional[str] None, # 线程 ID缺省自动生成 project_name: Optional[str] None, # Opik 项目名用于 trace 归类 **app_kwargs: Any, # 透传给 app 的额外关键字参数 ) - Dict[str, Any]返回字典包含三个键thread_id、conversation_history本轮完整消息列表、project_name。被集成应用函数的约定被测app的签名须为app(message: str, *, thread_id: str, **kwargs) - Dict[str, str]返回{role: assistant, content: ...}形式的字典。run_simulation会自动用track包装未追踪的应用并通过opik_args注入trace.thread_id与包含turn、project_name的 metadata从而把每一轮都归属到同一个线程下源码第 46-80 行。官方文档给出的完整集成示例from opik.simulation import SimulatedUser, run_simulation from opik import track track def customer_service_agent(user_message: str, *, thread_id: str, **kwargs): # 你的 Agent 逻辑内部管理对话历史 return {role: assistant, content: I understand your concern...} # 用多个人物做测试 personas [ You are a frustrated customer who wants a refund, You are a happy customer who wants to buy more products, You are a confused user who needs help with setup ] for i, persona in enumerate(personas): simulator SimulatedUser(personapersona) simulation run_simulation( appcustomer_service_agent, user_simulatorsimulator, max_turns5, project_namecustomer_service_evaluation ) print(fSimulation {i1} completed: {simulation[thread_id]})循环内部的容错与健壮性run_simulation对异常做了双层防护源码第 82-100 行若app调用抛出异常会用{role: assistant, content: Error processing message: ...}兜底仿真不中断若app返回的不是含role/content的字典会强制转成{role: assistant, content: str(...)}空返回则使用No response。第一轮的消息取自initial_message未提供时由user_simulator.generate_response([])生成之后每一轮都由模拟器基于累计的对话历史生成用户消息——注意这里run_simulation会维护一份conversation_history供模拟器使用而业务侧的完整历史由 app 自己通过thread_id管理两者职责分离。八、最佳实践把 Persona 写细写具体包含身份、情绪、诉求、语言风格的人设描述能显著提升行为一致性过于笼统的描述容易产生漂移。按场景选模型快速冒烟测试用速度快的模型降低成本需要逼真对话时换用能力更强的模型SimulatedUser的模型参数按实例独立配置方便分组测试。确定性测试优先用固定回复需要严格复现回归场景时用fixed_responses关闭 LLM 随机性需要评估 LLM 应用在开放式对话中的表现时再切回模型生成。主动管理上下文长度当前实现不会自动截断历史长对话请自行限制传入generate_response的消息条数避免超出模型 token 上限。善用兜底机制LLM 失败时类会返回Im having trouble responding right now. (...)而run_simulation也会对 app 异常兜底——把这两层机制当作仿真稳定性的默认保障不必自行再包一层 try/except。九、源码级注意事项Notes模型一致性SimulatedUser通过 Opik 的模型工厂models_factory.py创建 LLM 后端与 Opik 评估器如 LLM-as-judge 指标使用同一套模型解析与追踪机制保证配置和追踪行为一致。返回值是纯字符串generate_response返回的是文本而非{role: user, ...}字典组装消息结构是上层应用的责任run_simulation已代为处理。persona 即 system promptpersona 文本被完整拼接进 system prompt因此其中的指令性描述如说话简短先抱怨再提诉求会被 LLM 遵循。固定回复循环取模内部计数器_response_index自增并对len(fixed_responses)取模列表耗尽后从头开始且该计数器在实例生命周期内持续累计。对应测试用例行为细节可对照单元测试 test_simulated_user.py构造、固定回复循环、LLM 调用、异常兜底、长历史、test_simulator.py 以及集成测试 test_simulation_integration.py 进一步验证。十、小结SimulatedUser把用户这一抽象实体封装为一个可配置、可复用的对象用persona控制行为风格用model接入模型工厂用fixed_responses保证确定性再配合run_simulation的自动追踪与多轮驱动即可低成本地为客服、RAG 问答、Agent 等对话应用搭建自动化仿真与评估流水线。结合仓库源码可以看出其实现刻意保持简单——字符串输入输出、显式系统提示词、异常兜底这些细节共同保证了它在生产级评估链路中的稳定与可预测。【免费下载链接】comet-llmDebug, evaluate, and monitor your LLM applications, RAG systems, and agentic workflows with comprehensive tracing, automated evaluations, and production-ready dashboards.项目地址: https://gitcode.com/GitHub_Trending/co/comet-llm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻