RAG-DIVE:构建动态交互式评估框架,破解多轮对话RAG系统评测难题
1. 项目概述:为什么我们需要一个全新的RAG评估框架?
如果你最近在折腾基于大语言模型的检索增强生成系统,特别是那些需要处理多轮对话的复杂场景,那你肯定对“评估”这件事头疼不已。传统的RAG评估方法,比如扔进去一堆静态的问答对,跑个脚本看看召回率和答案准确率,在面对单轮、事实性强的任务时还能应付。但一旦进入多轮对话的深水区,问题就全暴露了:用户的问题会随着对话上下文动态演变,答案的质量不仅取决于单次检索的精准度,更依赖于模型对历史对话的理解、信息整合以及长期记忆的维持能力。这时候,再用静态的“开卷考试”去衡量一个动态的、交互式的系统,无异于刻舟求剑。
这就是“RAG-DIVE”这个动态交互式评估框架诞生的背景。它不是一个简单的指标计算器,而是一个模拟真实人类对话行为的“压力测试场”。DIVE这个名字很形象,它要求评估者“潜入”到多轮对话的复杂流中,去动态地(Dynamic)、交互式地(Interactive)验证(Validate)和评估(Evaluate)系统的每一个环节。我经历过太多次这样的尴尬:一个在静态测试集上得分很高的RAG系统,一上线跟用户聊上三五轮,就开始出现答非所问、信息遗忘或者前后矛盾的情况。问题的根源在于,我们缺少一个能够系统性制造并检验这些“对话压力”的工具。
RAG-DIVE瞄准的正是这个痛点。它通过构建一个可编程的、智能的对话模拟器,来驱动整个评估过程。这个模拟器不仅会提问,还会根据系统的回答,像真人一样决定下一步是追问、澄清、转换话题还是深入探讨。在这个过程中,框架会从多个维度实时收集数据:每一轮检索到的文档相关性是否在衰减?模型生成的答案是否与历史信息保持一致?系统能否处理指代消解(比如用户说“上面提到的那个方法”)?这些动态的、贯穿对话生命周期的指标,才是衡量一个多轮对话RAG系统是否健壮的关键。
2. 核心设计思路:从静态快照到动态过程
2.1 传统评估方法的局限与破局点
在深入DIVE的架构之前,我们得先看清它要解决什么问题。传统的RAG评估,无论是基于BLEU、ROUGE的文本匹配,还是基于LLM-as-a-Judge的答案评分,大多遵循一个“查询-检索-生成-评分”的单次循环。评估数据集通常是一个个孤立的(query, ground_truth)对。这种方法至少存在三个致命缺陷:
- 上下文断裂:它无法评估系统在连续对话中维护和利用上下文的能力。比如,用户第一轮问“特斯拉Model 3的续航是多少?”,第二轮接着问“它比Model Y长吗?”。一个好的系统需要记住“它”指代Model 3,并且能关联两轮信息进行比较。静态评估完全丢失了这种关联性。
- 状态遗忘:多轮对话中,系统的“状态”(如已确认的用户意图、已提供的部分信息)是不断累积的。静态评估每次都是“冷启动”,无法检验系统是否有状态管理机制,是否会重复提问或提供矛盾信息。
- 交互策略缺失:真实的对话包含澄清、确认、追问等策略。例如,当用户查询模糊时,系统是否会主动提问以澄清需求?传统评估无法测试这种主动交互能力,而这恰恰是影响用户体验的关键。
RAG-DIVE的破局思路,是将评估对象从一个“函数”转变为一个“状态机”。它不再仅仅关心单次输入输出的正确性,而是关心这个状态机在一条由智能体驱动的、不可预测的对话路径上的整体表现。其核心设计可以概括为“一个驱动,两类评估,三层指标”。
2.2 “一个驱动”:智能对话模拟器
这是DIVE框架的大脑和引擎。它本身通常也是一个LLM(例如GPT-4或Claude-3),被赋予一个特定的“用户角色”和目标。例如,角色可以是“一个想学习机器学习但毫无基础的新手”,目标是“通过对话弄清楚监督学习和无监督学习的区别,并各举一个例子”。
这个模拟器的关键能力在于:
- 目标导向的对话生成:它不是随机提问,而是围绕既定目标,有计划地展开多轮对话。可能会从宽泛的问题开始,逐步深入,并可能在遇到模糊回答时主动发起澄清。
- 上下文感知:模拟器完全“记得”之前所有轮次的对话历史,包括它自己说过的话和系统给出的回答。这使得它能提出连贯的后续问题,比如“你刚才提到的那个分类算法,具体怎么实现?”
- 策略多样性:我们可以为模拟器编程,使其具备不同的对话策略。比如,“打破砂锅问到底”型策略会持续追问细节直到满意;“跳跃思维”型策略会频繁转换话题,测试系统的上下文切换能力;“挑剔型”策略会故意抓住系统回答中不严谨的地方进行质疑。
通过配置不同的模拟器角色、目标和策略,我们可以生成海量、多样且贴近真实场景的多轮对话测试用例,这是手工构造测试集无法比拟的。
2.3 “两类评估”:过程评估与结果评估
在模拟器驱动对话的过程中,RAG-DIVE并行执行两类评估:
过程评估:关注对话“旅途”中的每一步。这包括:
- 检索质量追踪:每一轮,系统检索出的文档块(chunks)的相关性得分如何变化?是否存在随着对话深入,检索精度下降的问题?
- 生成一致性检查:系统本轮的回答,是否与它自己在之前轮次中提供的信息相矛盾?例如,前一轮说“方案A耗时3天”,后一轮又说“方案A通常需要1周”,这就触发了不一致警报。
- 指代消解能力:系统是否能正确理解“它”、“前者”、“这个方法”等指代性表述,并将其关联到正确的历史实体上?
结果评估:关注对话“终点”的成果。当模拟器认为目标已达成或对话自然结束时,会对整个对话序列进行整体评估:
- 目标完成度:最初设定的对话目标(如“弄清楚两个概念的区别并举例”)是否被圆满达成?
- 信息完整性:系统提供的所有信息,作为一个整体,是否覆盖了目标所需的核心要点,且没有重大遗漏或错误?
- 用户体验评分:模拟器可以作为一个挑剔的用户,从流畅性、帮助性、清晰度等维度对整体对话感受进行打分。
2.4 “三层指标”:从微观到宏观的度量体系
基于上述评估,DIVE会产出一个分层的指标报告:
- 轮次级指标:最细粒度。包括每一轮的检索命中率(Hits@K)、答案事实一致性分数、是否包含幻觉等。这有助于定位具体哪一轮出了问题。
- 对话级指标:核心创新层。包括对话连贯性得分(衡量回答与历史上下文的关联度)、信息累积有效性(后期回答是否有效利用了前期已交换的信息)、主动澄清率(系统在模糊查询下主动提问的比例)等。
- 会话级指标:最宏观。包括目标达成率、平均对话轮次(效率指标)、整体用户满意度(由模拟器LLM评分)等。
这个“驱动-评估-指标”的三位一体设计,使得RAG-DIVE能够全方位地给一个多轮对话RAG系统“做体检”,而不仅仅是“量身高”。
3. 实操搭建:从零构建一个简易的RAG-DIVE评估环境
理论说得再多,不如动手搭一个。下面我将以一个评估“技术文档问答机器人”多轮对话能力的场景为例,展示如何用开源工具快速搭建一个简易版的RAG-DIVE评估流水线。我们会使用LangChain作为框架,Chroma作为向量库,并利用GPT-4 Turbo同时扮演“对话模拟器”和“评估法官”。
3.1 环境准备与核心组件定义
首先,安装必要依赖,并定义几个核心类。
pip install langchain langchain-openai chromadb pydanticimport os from typing import List, Dict, Any, Optional from pydantic import BaseModel, Field from langchain_openai import ChatOpenAI from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings from langchain.schema import Document, SystemMessage, HumanMessage, AIMessage # 1. 定义对话轮次记录 class TurnRecord(BaseModel): turn_id: int role: str # "user" or "assistant" message: str retrieved_docs: List[Document] = Field(default_factory=list) # 本轮检索到的文档 retrieval_scores: List[float] = Field(default_factory=list) # 对应相关性分数 consistency_score: Optional[float] = None # 与历史的一致性得分 # 2. 定义对话模拟器 class DialogueSimulator: def __init__(self, llm, persona: str, goal: str): self.llm = llm self.persona = persona self.goal = goal self.conversation_history: List[Dict[str, str]] = [] def generate_query(self) -> str: """基于当前对话历史和目标,生成下一句用户提问""" prompt = f""" 你是一个{self.persona}。你的对话目标是:{self.goal}。 以下是到目前为止的对话历史: {self._format_history()} 请根据你的目标和对话历史,生成一句自然、连贯的下一轮用户提问或陈述。 只输出提问内容,不要添加任何解释。 """ response = self.llm.invoke(prompt) query = response.content.strip() self.conversation_history.append({"role": "user", "content": query}) return query def evaluate_goal_completion(self, full_dialogue: List[TurnRecord]) -> Dict[str, Any]: """评估整体目标完成度""" dialogue_text = self._format_dialogue_for_eval(full_dialogue) prompt = f""" 作为{self.persona},你的初始目标是:{self.goal}。 以下是完整的对话记录: {dialogue_text} 请从0到10分评估你的目标在多大程度上得到了满足(10分表示完全满足)。 同时,简要说明打分的理由(1-2句话)。 请以JSON格式输出:{{"score": x, "reason": "..."}} """ response = self.llm.invoke(prompt) # 这里需要解析LLM的JSON输出,简化处理 import json try: return json.loads(response.content) except: return {"score": 0, "reason": "Evaluation failed"} def _format_history(self) -> str: # 格式化历史记录 pass def _format_dialogue_for_eval(self, dialogue: List[TurnRecord]) -> str: # 格式化完整对话 pass # 3. 定义评估器 class DIVE_Evaluator: def __init__(self, llm): self.llm = llm def assess_consistency(self, current_answer: str, history: List[TurnRecord]) -> float: """评估当前回答与历史信息的一致性""" if not history: return 1.0 # 无历史,默认一致 history_text = "\n".join([f"Turn {t.turn_id}: {t.message}" for t in history if t.role == "assistant"]) prompt = f""" 请判断以下“最新回答”是否与已有的“历史回答”存在事实性矛盾或冲突。 只考虑客观事实冲突,忽略表述方式的差异。 历史回答: {history_text} 最新回答: {current_answer} 如果存在任何明确的事实矛盾,输出0。 如果没有发现矛盾,输出1。 如果无法确定(例如信息不足),输出0.5。 只输出一个数字。 """ response = self.llm.invoke(prompt) try: score = float(response.content.strip()) return max(0.0, min(1.0, score)) # 钳制在0-1 except: return 0.5 def assess_retrieval_relevance(self, query: str, docs: List[Document]) -> List[float]: """评估检索文档与查询的相关性(简易版)""" scores = [] for doc in docs: prompt = f""" 查询:“{query}” 文档内容:“{doc.page_content[:500]}” # 截取部分内容 请判断该文档内容与查询的相关性,评分从0到1(1表示高度相关)。 只输出分数。 """ response = self.llm.invoke(prompt) try: score = float(response.content.strip()) scores.append(score) except: scores.append(0.0) return scores注意:上述评估器使用了同一个LLM进行多次调用,在实际生产中成本较高。为了优化,可以考虑使用更小的、专门微调过的评估模型,或者采用基于嵌入向量的相似度计算作为一致性检查的初筛。
3.2 构建被测RAG系统与评估流水线
接下来,我们构建一个简单的RAG系统作为被测对象,并编写主循环。
# 初始化LLM和向量库 os.environ["OPENAI_API_KEY"] = "your-api-key" llm_gpt4 = ChatOpenAI(model="gpt-4-turbo-preview", temperature=0) embedding = OpenAIEmbeddings() # 假设我们已经有一个包含技术文档的Chroma向量库 # vectorstore = Chroma(persist_directory="./tech_docs_db", embedding_function=embedding) class SimpleRAGSystem: def __init__(self, vectorstore, llm): self.vectorstore = vectorstore self.llm = llm self.dialogue_context = [] # 维护对话上下文 def respond(self, user_query: str) -> (str, List[Document]): """核心响应函数:检索+生成""" # 1. 检索(考虑上下文) expanded_query = self._expand_query_with_context(user_query) docs = self.vectorstore.similarity_search(expanded_query, k=3) # 2. 生成 prompt = self._construct_prompt(user_query, docs) response = self.llm.invoke(prompt) answer = response.content # 3. 更新上下文 self.dialogue_context.append(HumanMessage(content=user_query)) self.dialogue_context.append(AIMessage(content=answer)) return answer, docs def _expand_query_with_context(self, query: str) -> str: """利用最近几轮对话历史扩展查询,改善检索""" if len(self.dialogue_context) < 2: return query # 简单拼接最近一轮的QA recent_history = self.dialogue_context[-4:] # 取最近两条消息 history_text = "\n".join([msg.content for msg in recent_history]) return f"当前问题:{query}\n相关对话历史:{history_text}" def _construct_prompt(self, query, docs): # 构建包含上下文和检索结果的提示词 context = "\n\n".join([doc.page_content for doc in docs]) history = "\n".join([f"{msg.type}: {msg.content}" for msg in self.dialogue_context[-6:]]) # 最近3轮 prompt = f"""你是一个技术文档助手。请基于以下提供的文档片段和对话历史,回答用户问题。 如果文档中没有明确信息,请如实告知你不知道,不要编造信息。 相关文档: {context} 对话历史: {history} 用户问题:{query} 请给出清晰、准确的回答:""" return prompt # 主评估循环 def run_dive_evaluation(rag_system, simulator, evaluator, max_turns=10): """运行一次完整的DIVE评估会话""" dialogue_log = [] print("=== DIVE评估会话开始 ===") for turn in range(max_turns): print(f"\n--- 第 {turn+1} 轮 ---") # 1. 模拟器生成用户问题 user_query = simulator.generate_query() print(f"[用户] {user_query}") dialogue_log.append(TurnRecord(turn_id=turn+1, role="user", message=user_query)) # 2. RAG系统响应 answer, retrieved_docs = rag_system.respond(user_query) print(f"[助手] {answer[:200]}...") # 截断显示 # 3. 评估本轮过程 # 3.1 评估检索相关性 retrieval_scores = evaluator.assess_retrieval_relevance(user_query, retrieved_docs) # 3.2 评估生成一致性 past_assistant_turns = [t for t in dialogue_log if t.role == "assistant"] consistency_score = evaluator.assess_consistency(answer, past_assistant_turns) # 4. 记录本轮结果 assistant_turn = TurnRecord( turn_id=turn+1, role="assistant", message=answer, retrieved_docs=retrieved_docs, retrieval_scores=retrieval_scores, consistency_score=consistency_score ) dialogue_log.append(assistant_turn) # 5. 简单判断是否提前结束(例如,模拟器可以内置一个目标达成判断) # 这里简化处理,运行固定轮次 print("\n=== 对话结束,开始最终评估 ===") # 最终评估:目标完成度 final_eval = simulator.evaluate_goal_completion(dialogue_log) print(f"目标完成度评分:{final_eval['score']}/10") print(f"理由:{final_eval['reason']}") # 计算对话级指标 avg_retrieval_score = np.mean([s for t in dialogue_log if t.role=='assistant' for s in t.retrieval_scores]) avg_consistency_score = np.mean([t.consistency_score for t in dialogue_log if t.role=='assistant' and t.consistency_score is not None]) print(f"平均检索相关性得分:{avg_retrieval_score:.3f}") print(f"平均回答一致性得分:{avg_consistency_score:.3f}") return dialogue_log, final_eval # 初始化各个组件 # vectorstore = ... # 你的向量库 # rag_system = SimpleRAGSystem(vectorstore, llm_gpt4) # simulator = DialogueSimulator(llm_gpt4, persona="一个正在学习Python异步编程的初级开发者", goal="搞清楚asyncio.create_task和asyncio.gather的区别与使用场景") # evaluator = DIVE_Evaluator(llm_gpt4) # results = run_dive_evaluation(rag_system, simulator, evaluator, max_turns=6)这个简易流水线已经具备了DIVE框架的核心雏形:动态生成对话、逐轮评估检索与一致性、最终进行目标达成度评判。你可以通过更换不同的模拟器“人设”和“目标”,来对你的RAG系统进行多角度的压力测试。
4. 核心评估维度的深度解析与优化
搭建起框架只是第一步,如何解读和优化各项指标才是关键。下面我们深入聊聊几个核心评估维度在实际项目中意味着什么,以及如何根据评估结果进行系统改进。
4.1 检索质量衰减:多轮对话中的“记忆衰退”现象
在多轮对话中,一个常见的问题是检索质量随着轮次增加而下降。在DIVE评估中,你会观察到avg_retrieval_score曲线可能呈下降趋势。这通常有几个原因:
查询漂移:用户后续问题可能基于之前的答案,与初始问题的语义差距越来越大。如果你的检索仅基于当前轮次的查询,而没有融入对话历史,就很容易“跑偏”。
解决方案:实现查询重写或上下文增强检索。就像我们上面在
_expand_query_with_context函数里做的,将最近几轮的关键信息拼接到当前查询中。更高级的做法是用一个轻量级模型专门学习如何将对话历史摘要成一个对检索更友好的“增强查询”。向量空间污染:如果知识库文档本身包含大量相似或重复内容,随着对话深入,系统可能反复检索到相同或相似但不完全相关的文档块,导致精度下降。
解决方案:在检索阶段引入多样性筛选或最大边际相关性算法,避免返回高度相似的文档。同时,确保知识库的文档块(chunk)划分合理,具有清晰的语义边界。
实操心得:不要只看平均分,要绘制每一轮检索得分的折线图。如果发现从某一特定轮次开始得分骤降,就去分析那一轮的查询和上下文,往往能发现系统设计的薄弱环节。例如,当用户开始使用“它”、“那个方法”等指代词时,如果系统没有做好指代消解和查询扩展,检索就会失灵。
4.2 生成一致性与事实性维护
consistency_score低下是另一个致命问题,它直接导致用户信任崩塌。不一致可能表现为:
- 事实矛盾:前面说“过程需要5分钟”,后面说“需要半小时”。
- 建议冲突:前面推荐方案A,后面又贬低方案A。
- 属性矛盾:对同一个对象的描述前后不一致。
排查与优化思路:
- 检查检索源:首先确认前后矛盾的信息是否来源于不同的、且本身可能冲突的检索文档。如果是知识库内部不一致,需要先清洗和统一知识源。
- 增强提示工程:在给LLM的提示词中,明确强调“请检查你的回答是否与之前提供的信息一致”。可以尝试采用“思维链”方式,要求模型在生成最终答案前,先列出已知的历史事实。
- 引入显式状态记忆:对于关键事实(如用户确认的参数、选择的方法等),不要完全依赖LLM的隐式记忆。可以在系统侧维护一个简单的“对话状态表”,在生成答案时,将这些确认过的状态作为硬性约束条件注入提示词。
- 后处理校验:生成答案后,用一个轻量级的“一致性校验器”模型(比如Qwen2.5-7B这种尺寸的),专门对比新答案和历史答案,发现矛盾则触发修正或澄清流程。
4.3 指代消解与上下文关联能力
这是多轮对话的灵魂,但在静态评估中几乎无法测试。在DIVE评估中,你可以通过设计模拟器的对话策略来专项测试。
- 测试用例设计:让模拟器大量使用“它”、“前者”、“你刚才说的那个”等指代。评估时,可以手动或自动检查系统回答是否成功关联了正确的前文实体。
- 评估方法:除了用LLM评估一致性,可以专门设计一个指代消解评估模块。输入是当前问题(含指代词)和对话历史,输出是模型解析出的指代对象(原文片段)。通过计算与真实指代对象的匹配度来打分。
- 系统优化:在检索前,增加一个“指代解析”步骤。使用一个专门的NER或共指消解模型(如Stanford CoreNLP或spaCy的组件)处理当前查询,将指代词替换为具体的实体名称,然后再进行检索和生成。虽然增加了复杂度,但对提升长对话体验至关重要。
5. 常见问题、挑战与实战避坑指南
在实际部署和运行RAG-DIVE框架时,你会遇到一些典型的挑战。以下是我从多次实践中总结出的“避坑指南”。
5.1 模拟器“脱离角色”或“目标漂移”
问题:你设定模拟器是一个“新手”,但它可能突然问出非常专业、深入的问题,脱离了预设的人设。或者对话进行到一半,它完全忘记了初始目标,开始聊无关话题。
解决方案:
- 强化系统提示词:在模拟器LLM的提示词中,反复、强调性地描述角色和目标。可以使用“你是一个...,你的核心目标是...,在整个对话中请始终牢记这一点...”这样的结构。
- 短期记忆注入:在每一轮生成问题的提示词中,不仅包含对话历史,还要再次明确提及初始目标。
- 设置检查点:每进行3-5轮,就让模拟器(或另一个监督LLM)自检一下:“当前的对话是否还在朝着目标X推进?”如果偏离,则引导其回到正轨。
- 使用更可控的模型:相比于全能的GPT-4,使用经过指令微调、遵循性更好的模型(如Claude-3 Haiku)作为模拟器,有时反而能更稳定地扮演特定角色。
5.2 评估成本高昂与延迟
问题:使用GPT-4这样的顶级模型作为评估器,每一轮都要调用多次(检索评估、一致性评估),整个对话下来成本极高,速度也慢。
优化策略:
- 分层评估:不是每一轮、每一个维度都用大模型。对于检索相关性,可以先用嵌入向量余弦相似度做一个快速、廉价的初筛,只有分数处于中间模糊地带的(比如0.3-0.7),才动用大模型做精细判断。
- 轻量级评估模型:训练或微调一个小型模型(如200M-1B参数)专门用于特定评估任务,如事实一致性判断。虽然绝对性能可能略逊于GPT-4,但成本可降低2个数量级,且延迟极低。
- 异步与批量评估:不要在线同步等待评估结果。可以将整个对话日志保存下来,事后集中、批量地进行评估,利用大模型的批量处理接口降低成本。
- 关键轮次采样:不必评估所有轮次。可以重点评估:1)第一轮(冷启动表现);2)涉及指代或话题转折的轮次;3)最后几轮(长期记忆表现)。
5.3 评估结果的主观性与波动性
问题:LLM作为评估法官,其打分本身存在一定主观性和不稳定性。同一段对话,多次评估可能得到略有差异的分数。
应对方法:
- 多数投票或平均分:对于关键评估(如目标完成度),使用同一个提示词让评估模型运行3-5次,取平均分或众数作为最终结果,以平滑随机性。
- 设计更客观的提示词:避免“请评价好坏”这种模糊指令。改为提供清晰的评分标准和范例。例如:“如果回答直接引用了文档Y中的原话‘过程需要5分钟’,且历史中未提及时间,则一致性得分为1;如果引用的是文档Z的‘过程需要半小时’,则得分为0。”
- 结合自动化指标:在可能的情况下,用客观指标补充主观评分。例如,“指代消解成功率”可以通过规则(是否成功链接到前文实体)来计算;“检索命中率”可以通过人工标注的文档相关性来判断。
- 人工校准:定期抽样一些评估案例,进行人工复核。根据人工判断与模型判断的差异,反过来调整和优化评估提示词。
5.4 如何设计有效的对话目标与策略
挑战:模拟器的对话目标和策略直接决定了测试的覆盖度和深度。设计得不好,测试就会流于表面。
设计原则:
- 从用户故事出发:不要凭空想象。分析你产品的真实用户日志,提取常见的多轮对话模式。例如:“用户先问概念A,再问A与B的区别,最后问如何实现A”就是一个典型的三段式目标。
- 覆盖关键失败模式:针对你已知的系统弱点设计“攻击性”策略。比如,如果你的系统在话题跳跃时表现差,就设计一个“频繁跳话题”的模拟器。
- 由浅入深:目标应该具有层次性。初级目标测试基本功能(如单轮问答),中级目标测试连贯性(如多轮澄清),高级目标测试复杂推理(如基于多轮信息的决策)。
- 混合策略:一个模拟器可以配备多种策略,并在对话中随机或按条件切换。例如,70%的时间按部就班,30%的时间突然扮演“挑剔的用户”挑战系统的回答。
RAG-DIVE框架的价值,在于它将多轮对话RAG系统的评估从一个模糊的、定性的感觉,变成了一个可量化、可重复、可调试的工程过程。通过持续运行DIVE测试,你可以像拥有一个永不疲倦的、苛刻的测试团队一样,不断发现系统的边界和弱点,并针对性地进行优化。它带来的不仅是评估方式的革新,更是开发范式的转变——从“构建-静态测试”转向“构建-动态交互测试-迭代”的闭环。当你发现你的系统能够在各种“戏精”模拟器的连番拷问下依然保持稳定和可靠时,你对上线后的真实用户体验,才会有真正的信心。