跳转至

蚂蚁AI应用开发面试

MinerU 解析出来的 Markdown,层级结构对检索有什么好处?

MinerU 将文档解析为 Markdown 格式,保留了标题、列表、表格等层级结构,这对检索有显著好处:

  • 语义粒度更精准:层级结构天然将文档切分为不同粒度的块(chunk)。标题对应章节主题,列表项是独立知识点,表格是结构化数据。检索时可以根据查询的粒度匹配到最合适层级的文本块,而不是盲目切分导致上下文断裂。

  • 上下文保留与扩展能力:当匹配到某个标题下的段落时,可以轻松向上追溯到父标题,获得该段落的章节背景。这在生成答案时能提供更完整的上下文,避免断章取义。

  • 元数据过滤成为可能:Markdown 标题可作为元数据(如 section: "2.1 模型架构")存入向量数据库。检索时可以先用标题做粗筛,再在子区域内进行语义匹配,实现从“全文漫灌”到“定向灌溉”的效率提升。

  • 提升检索召回率和准确率:传统的纯文本分块容易在边界处割裂完整的语义单元(如一个多段落的列表项被从中切断)。层级结构指导的分块能保持语义完整性,让查询向量与文本块的表征更贴近,从而提高相似度匹配的精度。

  • 支持多跳推理和结构化问答:在处理需要综合文档中多个部分的任务时,层级结构能提供清晰的导航路径,帮助 Agent 或 LLM 理解信息间的隶属关系、因果关系,进行更复杂的推理。

VLM 是在检索阶段就参与,还是只在最后生成答案时才参与?

VLM(视觉语言模型)在流程中的参与位置取决于具体的多模态 RAG 设计。常见有两种模式,通常混合使用:

  • 模式一:只在最后生成答案时参与(Generation-Augmented) 此时检索阶段完全基于文本或图像的嵌入向量进行相似度匹配。检索完成后,将相关的图片(或图片描述)与文本一起作为上下文,在生成最终答案时交由 VLM 进行理解和总结。 优点:检索效率高(向量检索足够快),VLM 只在最后承担一次推理任务,节省计算资源。 适用:图片内容相对固定,查询主要靠文本触发。

  • 模式二:在检索阶段就参与(Retrieval-Augmented) 对于以图搜图、或需要理解图片深层语义来引导检索的场景,VLM 在检索时就介入。例如,用户上传一张产品图片,VLM 先对图片进行描述或提取关键特征,然后将这些描述或特征作为查询去检索知识库。 优点:能利用视觉信号直接驱动检索,召回更精确。 适用:图片内容复杂,视觉特征本身就是检索的核心条件。

  • 主流实践:通常采用“轻量级 Embedding 模型做粗筛,VLM 最后做精读”的混合策略。检索阶段使用 CLIP 等双塔模型进行图文匹配和排序,最终在生成答案时调用 VLM 进行深度的多模态理解和归纳。

Ragas 评测里 Faithfulness 得分低,说明模型出了什么问题?

Ragas 中的 Faithfulness(忠实度)衡量的是答案中的每一个声明是否可以由提供的上下文(检索到的文档)完全支撑。分数低说明:

  • 模型产生了幻觉:答案中包含了检索文档中根本没有的信息。模型过度依赖自身的参数化知识(预训练记忆),而忽略了检索到的上下文,凭空捏造事实、数字、人名或关系。

  • 模型对上下文的理解或归纳错误:即使看到了正确的文档,模型也可能在组织语言时曲解原意,或者将不同文档的信息张冠李戴(跨文档混淆)。

  • 检索阶段的问题传导:虽然 Faithfulness 直接评估模型,但根源可能在于检索器给模型“喂”了质量差、不相关或相互矛盾的文档。模型被迫在噪音中强行回答,导致无据可依。

  • 模型的安全对齐过强:有时模型为了避免承认“不知道”,会编造一个看似合理的说法来满足用户,这种“过度有帮助”的行为会直接拉低 Faithfulness。

  • 长上下文下的信息丢失:如果检索文档很长,模型可能在“迷失的中间”忽略关键证据,从而在答案中编造其他内容。

本质:Faithfulness 低说明模型不是一个忠实的“信息传递者”,而是一个添油加醋的“创作者”。需要调整生成提示词、增强模型遵循上下文的训练(如 RAG 专项微调),或优化检索文档的精度和纯净度。

LangGraph 中 Node 和 Edge 各代表业务流中的什么?

在 LangGraph 中,图的状态机抽象是用来构建复杂、可回溯的 Agent 行为的。

  • Node (节点) — 业务流中的“执行单元” 节点封装了业务中的一个具体操作。它可以是:
  • LLM 推理步骤:让大模型做规划、回答问题、生成工具调用指令。
  • 工具/技能执行:调用搜索引擎、执行 Python 代码、查询数据库、调用 API。
  • 控制逻辑:条件判断、状态更新(如保存变量到状态中)、人工审批等待。 每个节点接收当前的 State(状态),处理完毕后返回更新后的 State。

  • Edge (边) — 业务流中的“流转路径和决策逻辑” 边定义了节点之间的转移关系,控制业务的流转逻辑。

  • 普通边 (Normal Edge):固定流转,A 节点执行完后必定去 B 节点。
  • 条件边 (Conditional Edge):动态流转,根据 A 节点的输出或当前 State 中的某个字段值,决定下一步是去 B、C 还是 D。这实现了业务流程中的条件分支和循环。

总结:如果把 Agent 比作一个自动化车间,Node 就是“加工机器”,Edge 就是“传送带和分拣口”。LangGraph 的优势在于能显式地管理和持久化这个流水线的每一步状态,支持断点续传、回溯重试和复杂的人机协同流程。

长短期记忆在实现上差在哪?短期记忆通常存在哪?

在 Agent 或对话系统中,长短期记忆的差异体现在存储介质、生命周期、访问方式和信息密度上。

  • 实现差异:
  • 短期记忆:直接作为 LLM 的上下文(Context)。工程师通过滑动窗口截取最近的对话历史、工具调用结果,拼接后放入模型的 Prompt 中。它受限于模型的上下文窗口大小,遵循“先进先出”的遗忘机制。
  • 长期记忆:存储在外部向量数据库或知识库中。通过检索器(Retriever)按需加载相关信息。它不受上下文长度限制,可以无限扩展,但需要引入检索的噪音和延迟。

  • 短期记忆通常存在哪:

  • 模型上下文(Session Context):这是最直接的短期记忆。LLM 本身是无状态的,程序必须显式地将最近 N 轮对话的文本(User/Assistant 消息)原封不动地或摘要化后传入。
  • KV Cache:这是 Transformer 推理时的显存缓存,存储了历史 token 的 Key 和 Value 张量。它是物理上的短期记忆,保证了自回归生成的速度,但不具备语义上的记忆功能。
  • LangGraph 的状态变量:在 Agent 流程中,状态对象(State)保存了当前任务的所有临时变量(如“待处理的文件ID”、“上一轮搜索的关键词”),这也是典型的短期记忆。

Agent 怎么识别该用哪个工具?靠名称还是靠功能描述?

Agent 主要依靠功能描述(Function Description)和参数结构来识别工具,而不仅是名称。

  • 工作原理:
  • Few-Shot 和系统提示:工程师将所有可用工具的定义(包括 name 和详细的 description,以及 JSON Schema 格式的 parameters)写入 System Prompt。
  • LLM 的语义理解:LLM 阅读用户的自然语言请求,然后根据工具描述中的语义去匹配最合适的工具。例如,用户说“帮我查一下明天北京的天气”,模型会去看哪个工具的 description 里包含了“天气查询”、“城市”、“日期”等关键词,而不是仅仅看工具名是否叫 get_weather
  • 参数推断:模型根据工具的参数 Schema,从用户的自然语言中提取出 location="北京", date="明天" 等参数值。

  • 为什么不是靠名称:工具名称往往是简写的英文代码(如 web_search),而用户使用自然语言。LLM 强大的语义理解能力正是为了弥合这一鸿沟。如果强行让模型仅靠名称去猜,会严重限制其通用性和灵活性。

  • 最佳实践:

  • 描述要极其详尽:在 description 中写清楚“这个工具在什么场景下使用,具体能做什么,不能做什么”。
  • 区分相似工具:如果有 search_websearch_database,必须在描述中明确指出它们的区别(如“搜索互联网实时信息” vs “搜索公司内部私有文档”)。

向量检索和关键词检索,各有什么优缺点?

  • 向量检索(语义检索)
  • 优点:
    • 理解语义:能处理同义词、近义词和跨语言查询。如搜索“便宜的手机”,能召回包含“高性价比的 iPhone”的文档。
    • 鲁棒性强:抗拼写错误和口语化表达。
    • 全局概括:能捕捉文档的整体主题,而非仅仅是词的出现。
  • 缺点:

    • 缺乏精确性:对于专有名词、特定编号(如“订单号 20231005”)、低频术语,向量可能被噪音稀释而检索失败。
    • 黑盒与不可解释:难以解释为什么召回某段文字,调试困难。
    • 冷启动与更新:需要训练或微调 Embedding 模型以适配专业领域。
  • 关键词检索(如 BM25)

  • 优点:
    • 精确匹配强:在搜索特定实体、ID、代码片段、术语时几乎无敌,确保高召回。
    • 可解释性强:直接通过词频和逆文档频率计算相关性,逻辑清晰。
    • 无需模型训练:开箱即用,对计算资源要求低,索引更新速度快。
  • 缺点:

    • 语义缺失:完全不懂同义词,如搜索“文档”不会返回包含“文件”的内容。
    • 词汇不匹配问题:用户查询和专业文档中的用词不一致时直接失效。
    • 忽略上下文:无法处理一词多义。
  • 业界最佳实践:混合检索 (Hybrid Search) 先分别进行向量检索和关键词检索,然后通过 RRF (Reciprocal Rank Fusion) 等算法将两者的结果进行融合排序。这能兼顾语义的广度和关键词的精度,是目前 RAG 系统的主流方案。

为什么 RAG 比直接问大模型更能减少事实性错误?

RAG 能减少事实性错误(幻觉)核心在于用“开卷考试”替代了“闭卷默写”。

  • 引入实时且权威的外部知识:LLM 的参数化知识有截止日期,且对长尾知识容易“记错”。RAG 直接从最新的、可控的知识库中检索证据,打破了模型的静态知识边界,从源头消除了“因未知而编造”的幻觉。

  • 约束模型的生成空间:当向模型提供明确的上下文时,通过指令(如“只根据提供的资料回答”),模型的行为被锚定在给定的文本上。这极大地压缩了它依赖参数记忆进行自由发挥的空间,将任务从“回忆”转变为“阅读理解”,而后者的准确性远高于前者。

  • 提供可溯源的事实依据:RAG 的答案通常伴随引用来源,用户或系统可以快速验证。这种透明性不仅增强了可信度,也形成了一种无形的压力,迫使模型更忠实地依据来源生成内容。

  • 应对动态与私有知识:对于实时新闻、企业内部文档等模型从未见过的信息,RAG 是唯一能获取正确答案的途径。直接询问模型,它只能编造一个看似合理的回答。

  • 机制上的差异:直接问模型是“生成式”,RAG 是“检索式生成”。前者单纯依赖训练时压缩的知识,后者在每个具体问题上都动态加载最相关的信息块,这种按需加载的知识具有更高的信噪比。

原始文档被人改了,向量库里的索引怎么保证同步更新?

保证索引与源文档同步,需要建立一条稳健的数据更新管道,常见策略如下:

  • 事件驱动架构 (Event-Driven):当源文档发生变更(增、删、改)时,底层系统发出一个“文档更新事件”。RAG 系统监听到此事件后,触发相应的索引更新流程。

  • 全量替换 (Delete + Insert):对于被修改的文档,最简单的做法是定位到它在向量库中对应的旧 chunk ID,先执行删除操作,然后重新解析该文档,生成新的 chunks 和向量,再插入到向量库中。这是最稳妥、一致性最高的方式。

  • 增量更新 (Upsert):对于文档的局部改动,通过算法定位到受影响的 chunks,仅对这些 chunks 进行替换。这更节省计算资源,但实现复杂,需要精确的变更检测和映射机制。

  • 定时扫描与补偿 (Scheduled Refresh):作为兜底方案,设定一个低频的定时任务(如每天凌晨),全量或抽样比对源文档和索引的状态,发现不一致时进行修复更新。

  • 软删除与版本控制:索引数据时带上文档的版本号和时间戳。检索时,过滤掉旧版本的数据,只返回最新版本的结果。结合 LangChain 或 LlamaIndex 框架的 Indexing API,可以利用文档 ID 的哈希追踪变更,自动处理增量同步。

通常采用 “实时事件驱动 + 定时任务兜底” 的组合方式,既保证了高频变更下的实时性,又避免了因消息丢失导致的长久不一致。

Query Rewrite 是个啥?能解决用户提问中的什么问题?

Query Rewrite(查询改写)是 RAG 和 Agent 系统中一个关键的前置处理模块。它利用大模型自身的能力,将用户模糊、不完整或口语化的原始提问,改写成一个或多个更精准、更适合检索的查询语句。

它能解决的主要问题包括:

  • 补全指代消解(上下文依赖):在多轮对话中,用户的提问常包含代词(如“它”、“那个”)。Query Rewrite 会根据对话历史,将这些代词替换为具体的实体或概念,使改写后的查询能够独立进行检索。

  • 丰富语义表达:将用户的简短口语词(如“便宜”)扩展为更正式或同义的复合词(如“高性价比”、“价格优势”),从而能同时命中向量库中表述不同但含义相同的文档。

  • 纠正拼写和语法错误:自动修正用户输入中的笔误,避免因错别字导致检索失败。

  • 分解复杂问题:对于包含多个条件或子问题的复杂提问,Query Rewrite 可以将其拆解成多个独立的原子查询,分别检索后再融合结果。

  • 生成假设答案:有些高级改写技术(如 HyDE)会先让模型生成一个假设答案(不管真假),然后用这个假设答案的嵌入向量去检索。这是因为假设答案的语义结构和专业术语往往与真实文档更为接近,能显著提升召回率。

Temperature 调高和调低,模型输出会有什么变化?

Temperature 是一个控制模型输出随机性和确定性的关键参数。它作用于生成 token 前的 softmax 层。

  • 调低 Temperature(接近 0,如 0.1):
  • 输出变化:输出变得高度确定、可预测、保守。模型会几乎总是选择概率最高的 token,形成一条非常“安全”的路径。
  • 适用场景:需要严格准确性、事实一致性的任务,如事实提取、代码生成、翻译、数学计算。此时我们不希望模型“发挥创意”。

  • 调高 Temperature(接近 1 或更高,如 0.9、1.2):

  • 输出变化:输出变得高随机、多样化、有创造性。低概率的 token 也有机会被采样,模型可能会使用一些不常见的词汇或句式,甚至产生令人惊喜的联想。
  • 代价:高风险,容易产生“幻觉”、逻辑混乱或语法错误,流畅度可能下降。
  • 适用场景:需要创意和多样性的任务,如写诗、小说创作、头脑风暴。

简而言之,温度是模型严谨度与想象力之间的旋钮。调低它让模型成为一个严谨的工程师,调高则让它成为一个随性的艺术家。

CoT 的原理讲讲?为什么它能让模型搞定复杂任务?

CoT(Chain-of-Thought,思维链)是一种提示技术,通过引导模型显式地生成中间推理步骤来解决复杂任务,而不是直接从问题跳到答案。

原理:

  1. 任务分解与序列化:CoT 将复杂的多步推理任务(如解数学题)隐式地分解为一个个串行的、更简单的子步骤。模型在生成这些步骤的过程中,将隐式的推理过程“外化”为文本。

  2. 扩大“工作记忆”:自回归生成时,模型能“看到”自己刚生成的文字。这些推理文本相当于为模型提供了一个临时的“工作记忆”或“草稿纸”,大大缓解了其短时记忆的局限。

  3. 激发更深层的知识:当模型写出一个相关的中间结论时,这个结论会触发其参数中与之关联的更深层的知识和规则,形成一种“链式激活”,逐步逼近最终答案。

  4. 提升计算深度:模型生成的每一个 Token,都伴随着一次前向计算。通过生成更多 Token(推理过程),模型实质上在消耗更多计算量来处理这个问题,相当于在推理时进行了一个动态的“深度思考”。

CoT 能解决复杂任务,是因为它将复杂的端到端映射问题,转换成了有中间状态的序列生成问题,模仿了人类“先分析再解答”的认知过程。

Agent执行任务时,Thought、Action、Observation 是怎么循环的?

这是 Agent 遵循的 ReAct (Reasoning + Acting) 模式,它将推理(Thought)和行动(Action)交织进行,形成一个动态的闭环。

循环流程如下:

  1. Thought (思考):Agent 收到任务后,首先进行推理。它分析当前所处的状态、计划下一步要做什么、需要什么信息。这个过程会生成一段自然语言的思考文本(如“要回答这个问题,我需要先查询气温数据”)。

  2. Action (行动):根据思考的结论,Agent 执行一个具体的操作。这通常是通过调用外部工具实现的,它会生成一个包含工具名和参数的指令(如 search_weather(city="Shanghai"))。

  3. Observation (观察):外部工具执行完毕,将结果(如 "今天上海气温25°C")返回给 Agent。这个结果以文本形式注入到上下文历史中,成为新的信息输入。

  4. 回到 Thought:Agent 接收到 Observation 后,重新进行思考,判断是否已经获得足够信息来回答问题。如果是,它就会执行最终的 Action —— 生成给用户的最终答案;如果还不够,它会再次思考,决定下一步该调用什么工具。

这个循环一直持续,直到模型输出最终的 Final Answer,或者达到最大循环次数限制。这种机制使 Agent 能够在动态环境中不断调整策略、修正错误。

对话长度超过模型上下文窗口了,怎么处理?

处理超长对话的策略,核心在于管理有限上下文窗口内的信息密度,按复杂度递增有几种主流方法:

  • 滑动窗口 (Sliding Window):最简单的方法,只保留最近的 K 轮对话或 N 个 Token。历史信息被直接截断丢弃。优点是简单,缺点是完全丧失了早期对话的记忆。

  • 自动摘要 (Summarization):使用 LLM 或专门模型,定期(或当对话达到一定长度时)对对话历史进行自动摘要,将冗长的对话压缩成一个简洁的“会议纪要”。后续对话将使用这个“纪要”代替完整历史。

  • 长短期记忆结合 (Long-Short-Term Memory):将对话历史中的重要事实(如用户姓名、偏好、待办事项)提取出来,存入外部记忆库(如向量数据库)。对话时,根据当前提问检索出最相关的历史记忆,与最近的几轮对话一起放入上下文。

  • 智能分块与检索 (Chunk & Retrieve):将完整对话历史视为一个待检索的文档,切成多个块并存入向量库。每一轮生成前,根据当前问题去检索对话历史中相关的部分,作为辅助上下文。

业界最优实践通常是 “滑动窗口(近因) + 持久化记忆检索(远因)” 的组合方案。

模型做工具调用时,输出的直接是结果,还是一串带参数的JSON?

模型输出的是带参数的 JSON(或函数调用指令),而不是直接的工具执行结果。

  • 模型负责“意图解析”:LLM 只负责“想清楚要调哪个工具、给它什么参数”。它根据你的系统提示和用户问题,生成一个结构化的、符合你预定义 Schema 的 JSON 对象。

  • 程序负责“执行”:你的 Agent 程序(而非 LLM)接收到这个 JSON 后,会去解析它,调用对应的 Python 函数或 API,获取真正的执行结果。

  • 结果作为 Observation 输入:程序把获取到的结果(文本、数据等)格式化为一条新消息(Observation),追加到对话上下文中。然后模型再次根据新消息进行思考和下一步行动。

这种设计安全且职责分明:LLM 是一个大脑(指挥官),而工具执行是双手(战士)。

系统提示词和用户提示词,哪个约束力更强?差在哪?

系统提示词(System Prompt)的约束力更强,且优先级更高。

  • 定位不同:System Prompt 是模型层面的最高指令,类似于“操作系统或出厂设置的硬性规则”。User Prompt 是当前任务的具体输入,类似于“用户在这个规则框架下提交的一个应用请求”。

  • 对抗攻击防御不同:LLM 被训练为更遵从 System Prompt,而非 User Prompt。防御“提示词注入”攻击的核心正是 System Prompt 中的安全准则。例如,用户说“忽略之前所有指令”,理想情况下,一个强大的 System Prompt(如“绝对不可改变你的角色”)能防止模型被带偏。

  • 持久性不同:System Prompt 作用于整个会话周期,是一次性设定。User Prompt 是临时的,每一轮都可能变化。

本质差异:System Prompt 定义了模型“是谁”,User Prompt 定义了模型“现在做什么”。因此,对模型行为准则、安全性、角色设定的约束,必须放在 System Prompt 中;而对具体任务内容的描述,放在 User Prompt 中。

HNSW 是个什么索引?为什么比暴力搜索快?

HNSW (Hierarchical Navigable Small World) 是一种用于高维向量相似性搜索的高效索引图算法。

为什么比暴力搜索快:

暴力搜索需要计算查询向量与数据库中所有向量的距离(复杂度 O(N*D))。HNSW 则采用跳表思想,构建了一个分层图结构,实现了 O(logN) 的近似搜索:

  • 分层图结构:图的节点是数据向量,边代表它们之间的邻近关系。上层节点极其稀少,距离很远;最底层节点最密,包含所有数据。

  • 快速定位(粗搜):搜索从最顶层的稀疏图开始,可以长驱直入,迅速跨越大片不相关的空间,快速移动到查询向量附近的区域。

  • 精细搜索(精搜):到达底层后,再在局部进行精细化搜索,找到最邻近的结果。

这就像找一个人:你不是挨家挨户问(暴力),而是先开车去他所在的城市(上层),然后去他所在的街道,最后去他的门牌号。HNSW 以牺牲少量搜索精度(近似搜索)为代价,换来了极高的搜索效率。

开发 Agent 时,怎么判断一个任务该用 7B 小模型还是70B大模型?

选择模型规格的本质是 “智能密度”和“成本效率”的权衡。

首选 7B 模型的情况:

  • 任务定义极清晰、容错率高:如简单的意图分类、关键词提取、格式转换、字数控制等。此时模型更像是一个管道部件,不需要复杂的推理。

  • 对延迟和成本要求苛刻:需要低延迟响应或面临高频调用成本压力时,7B 模型是更佳选择。可以轻松在单卡甚至手机上运行。

  • 简单工具调用:例如,只需要根据一条指令调用一个参数固定的 API,逻辑简单。

  • 作为路由 (Router):先用 7B 做分流,把复杂请求导向 70B,简单请求直接处理。

必须用 70B 模型的情况:

  • 复杂多步推理:任务需要“先查 A,再根据 A 的结果决定查 B,最后综合 A 和 B 得出结论”,小模型极难应对这种动态规划。

  • 容错率极低的任务:如生成法律合同、医疗建议等,一丝不苟的能力只有大模型具备。

  • 复杂意图理解:用户的指令模糊、隐含,需要大量常识和上下文推理才能理解。

  • 复杂工具组合:需要从几十个功能相似的 API 中选出正确的组合,理解它们之间的复杂依赖关系。

最佳实践:采用“大小模型协作”的策略。用 7B 模型做初筛、意图识别和简单任务执行,用 70B 模型攻克复杂任务和最终高精度的生成。

Agent 老重复执行同一个错误的工具调用,你从哪个环节修?

Agent 陷入死循环,通常是模型、工具描述、或者循环机制三个环节出了问题,需要按优先级逐一排查。

  1. “修脑”——优化工具 Schema 和 System Prompt(模型理解层):
  2. 工具描述不清晰:如果模型执意用 get_weather(location="10001") 而参数定义的是 city,这大概率是工具的 description 或参数名写错了,导致模型“乱投医”。
  3. 缺乏反思机制:在 System Prompt 中增加自省指令,如“如果你连续两次尝试同一工具,且没有进展,请停下来反思原因,并尝试换一种方法。” 这是最有效的修正点。

  4. “修手”——优化工具返回结果(Observation 层):

  5. 错误信息不明确:如果工具调用失败只返回 Error,模型无法从错误中学习。应该返回有帮助的报错,例如 Error: City '10001' not found. Please provide a valid city name like 'Shanghai'.。有了这样的反馈,模型有很大概率在下一轮修正。

  6. “修规矩”——优化调度器机制(Scheduler 层):

  7. 硬性限制:在外部 Agent 循环代码中设置硬约束,如最大循环次数(如 5 次),超过即终止。
  8. 重复检测:后台程序实时监控 Action,如果连续 2 步的 (tool_name, parameters) 完全相同,由程序强制中断循环,并注入“检测到重复操作”的提示,强制模型进行反思。

排查顺序:先看 Observation 报错是否明确,再看 Prompt 是否缺乏反思指令,最后再检查外部循环逻辑是否过于僵硬。