影石 AI Agent 开发一面

1、RAG 流程

RAG 一般分成离线和在线两部分。

离线侧主要做文档接入、清洗、切分、向量化和索引构建。文档来源可以是产品文档、帮助中心、接口文档、历史问答、内部知识库。清洗之后按段落、标题或者固定窗口做切片,再用 embedding 模型把文本转成向量,写入向量数据库,同时保留 metadata,比如文档类型、时间、部门、权限标签。

在线侧通常是用户问题进来后,先做 query 预处理,比如改写、纠错、意图识别,然后拿 query 去做检索。检索可以是向量检索、BM25 检索或者混合检索。召回到候选片段后,再做 rerank,把最相关的内容排到前面,最后拼接 prompt 和用户问题一起送给大模型生成答案。如果是 Agent 场景,还会在这个阶段结合工具调用、记忆和状态管理。

一个常见链路就是:

用户问题 -> Query改写 -> 检索召回 -> Rerank重排 -> 上下文构造 -> LLM生成 -> 返回答案

如果要写成代码流程,大概是这样:

def rag_pipeline(query):
    rewritten_query = rewrite_query(query)
    candidates = retrieve(rewritten_query)
    top_docs = rerank(rewritten_query, candidates)[:5]
    prompt = build_prompt(query, top_docs)
    answer = llm_generate(prompt)
    return answer

2、做过的项目的 Agent 应用场景

3、实习询问

4、记忆架构怎么做的

记忆架构一般分短期记忆和长期记忆。

短期记忆主要服务当前会话,保存最近几轮对话、用户当前目标、已经调用过的工具结果、中间推理状态。它的重点是保证多轮连续性,一般会放在 Redis、数据库或者会话上下文里。

长期记忆主要保存稳定事实,比如用户偏好、历史习惯、常见设置、过去交互中抽取出来的重要信息。长期记忆通常不会直接整段存聊天记录,而是抽取成结构化事实或者向量化语义记忆,后续需要时再召回。

一个相对完整的记忆架构会这样做:最近 N 轮原文直接保留,更早历史做摘要;摘要里再抽取稳定信息,写入长期记忆;用户下一次发起新问题时,先取最近上下文,再根据 query 去召回相关长期记忆,一起构造成 prompt。

class MemoryStore:
    def __init__(self):
        self.short_term = []
        self.long_term = {}

    def add_message(self, msg):
        self.short_term.append(msg)
        if len(self.short_term) > 6:
            self.short_term.pop(0)

    def save_fact(self, user_id, fact):
        self.long_term.setdefault(user_id, []).append(fact)

    def get_context(self, user_id):
        return {
            "recent": self.short_term,
            "facts": self.long_term.get(user_id, [])
        }

记忆不是存得越多越好,关键是怎么压缩和怎么召回。否则上下文会越来越长,最后模型反而抓不到重点。

5、RAG 效果如何提升?如果出现幻觉怎么办?

RAG 提升效果一般从召回、重排、上下文构造和生成约束四层做。

召回层面,首先要把文档切分做好,chunk 太大噪音多,太小语义不完整。其次可以做混合检索,不只依赖向量召回,还结合 BM25 和 metadata 过滤。对于 query 比较口语化或者存在错别字的场景,可以先做 query rewrite,再去检索。

重排层面可以加 rerank,把真正最相关的文档放到前面,减少低质量证据进入 prompt。上下文构造时不要简单拼接 topK,而是要控制长度、去重、合并相似片段。生成阶段要明确要求模型只能基于提供的证据回答,不允许扩写和脑补。

幻觉本质上通常来自三个原因:没召回到,召回错了,或者召回到了但模型没按证据回答。处理方式也对应这三层。没召回到就优化知识库、切片和检索;召回错了就优化排序和过滤;模型乱答就加约束和拒答机制。

一个常见的 Prompt 约束是:

请严格基于提供的参考资料回答。如果资料不足,请直接回答“当前知识库中没有足够依据”,不要自行补充。

如果系统已经上线,通常还会加置信度判断,比如召回分数低于阈值直接拒答,或者让模型返回“是否有充分证据”的字段,再由程序决定是否展示结果。

6、单个对话消耗的 token 大概多少?

这个没有绝对固定值,要看模型、系统 Prompt 长度、历史轮数、检索上下文长度、用户输入和输出字数。

如果是一个普通单轮问答,没有历史、没有长文档注入,可能输入加输出一共几百到一两千 token。要是带 RAG,上下文里塞了 3 到 5 个 chunk,再加系统提示和用户问题,输入很容易到 2000 到 5000 token。多轮对话再加历史消息,单次调用上万 token 也很常见。

如果是 Agent 场景,因为中间还会插入工具描述、工具调用结果、记忆摘要、状态信息,token 消耗通常比普通聊天更大。所以线上一般都会统计平均输入 token、平均输出 token、不同场景下的 token 分布,不能靠感觉估算。

比较常见的做法是接入统一日志,把每次请求的 prompt token、completion token、总耗时一起记录下来,然后按场景做分析。

7、单轮或多轮对话消耗 token 比较大的话,该怎么办?

单轮 token 大,通常是因为塞了太多检索内容、Prompt 太啰嗦或者工具描述过长。多轮 token 大,往往是历史消息不断累积造成的。解决思路都是做压缩和筛选。

单轮场景下,可以先减少无效上下文,比如 topK 不要拿太多,召回后先做 rerank,再只保留最相关的几个片段;对长文档做摘要压缩;把工具描述标准化,不要写成长篇自然语言。

多轮场景下,一般不会保留所有历史原文,而是保留最近几轮,把更早对话做摘要。还可以把用户的稳定信息抽成结构化记忆,而不是每次都把过去整段聊天带进来。

另外一个非常有效的方法是模型分级。复杂推理再走大模型,改写、分类、路由、摘要这些步骤可以交给小模型做。输出端也要限制 max_tokens,避免模型生成过长内容。

def compress_history(history, max_turns=6):
    recent = history[-max_turns:]
    summary = "此前主要讨论了设备离线、日志排查和重启处理方案"
    return recent, summary

核心就是四件事:裁剪历史、压缩上下文、减少冗余调用、把简单任务分流给小模型。

8、Function Calling 怎么设计比较稳?

Function Calling 设计稳定性的关键,不在于把函数注册进去,而在于 schema、约束和执行保护。

首先工具定义要清楚,包括工具名称、功能描述、参数类型、必填字段、取值范围、是否有副作用。参数越模糊,模型越容易生成错。其次不能把所有工具一股脑丢给模型,工具多的时候要先做候选筛选,不然模型乱选概率会明显上升。

执行时不能完全相信模型输出,程序层必须做 JSON 解析和参数校验。不合法的参数要么重试生成,要么直接报错。对于有副作用的工具,比如发消息、删除数据、修改配置,还要做权限控制和人工确认。

tools = [
    {

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

AI-Agent面试实战专栏 文章被收录于专栏

本专栏聚焦 AI-Agent 面试高频考点,内容来自真实面试与项目实践。系统覆盖大模型基础、Prompt工程、RAG、Agent架构、工具调用、多Agent协作、记忆机制、评测、安全与部署优化等核心模块。以“原理+场景+实战”为主线,提供高频题解析、标准答题思路与工程落地方法,帮助你高效查漏补缺.

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务