DEV Community

quarktimes
quarktimes

Posted on

2026-06-15 Senior Agent Architect Interview Questions

Q1: [流式UI渲染中断与状态修复]

难度: Senior
领域: 生产化AI / Agent架构
对应工作: 今天修复 Flutter 聊天界面 AI 内容流式显示的打字机效果中断问题,排查文本追加逻辑和换行处理缺陷。

题目:
在一个 AI 对话 Agent 的前端展示中,SSE(Server-Sent Events)流式传输 AI 的回复内容。偶发情况下,打字机效果会中断,后续内容不再显示;而在尝试修复时,如果不小心修改了截断逻辑,会导致只显示第一行文字。请设计一套通用的流式状态更新机制,能够处理以下情况:

  1. 网络抖动导致的数据包乱序或延迟。
  2. Markdown 渲染库对未闭合标签的截断导致白屏。
  3. 状态更新并发竞态导致文本被覆盖而非追加。 请给出关键的状态管理代码逻辑,并解释如何设计日志系统来快速区分“后端数据发送停止”还是“前端渲染逻辑错误”。

答案要点:

  1. 核心思路:
    采用 累加器模式 结合 不可变状态更新,引入 Buffer 来处理流式片段,并确保 Markdown 渲染具备“容错”或“延迟渲染”机制。关键是将“网络数据接收”与“UI 渲染”解耦。

  2. 技术方案(伪代码示例,模拟 React/Flutter 通用逻辑):

# 伪代码:流式状态管理器
class StreamMessageHandler:
    def __init__(self, message_id):
        self.message_id = message_id
        self.full_content = ""  # 完整文本
        self.last_chunk = ""    # 上一次渲染的片段(用于防抖或增量渲染)
        self.is_complete = False

    def append_chunk(self, chunk: str):
        # 1. 数据校验与日志(关键:原始数据入参必须落盘/打点)
        self._log_debug(f"Received chunk: {repr(chunk)}")

        # 2. 核心累加逻辑(防止覆盖)
        self.full_content += chunk

        # 3. 状态更新(触发UI重绘)
        updated_content = self.full_content

        # 4. 换行/截断保护
        # 如果检测到只有第一行,可能是截断逻辑错误,此时不应修改 full_content
        if "\n" not in updated_content and len(updated_content) < 100:
             self._log_warning("Potential truncation detected")

        self._log_debug(f"Updated full content length: {len(updated_content)}")
        return updated_content

    def mark_complete(self):
        self.is_complete = True
        self._log_info(f"Stream complete. Final length: {len(self.full_content)}")
Enter fullscreen mode Exit fullscreen mode
  1. 权衡分析:

    • 选择 A(全量重绘 vs 增量渲染): 全量重传给 Markdown 引擎性能开销大,但能保证格式正确(防止未闭合的 ** 导致整段加粗失效);增量渲染性能好,但极易出现样式闪烁或渲染错误。
    • 决策: 对于长文本(>500字),采用全量重绘配合防抖;对于短文本,可尝试增量。但在修复 Bug 阶段,优先全量重绘以确保数据完整性。
  2. 反面教训:

    • 只改截断不改追加: 今天在修复时,错误地以为文本太长被截断,结果修改了 Split 逻辑,导致只显示第一行。实际上问题在于 setState 时拿的是旧状态 oldText 而不是当前的 fullText
    • 日志缺失: 如果没有打印 chunk 的原始字节,永远不知道是后端没发数据,还是前端丢了数据。
  3. 量化指标:

    • 监控“流式中断率”:定义为 (预期Token数 - 实际渲染Token数) / 预期Token数。修复前约 2%,修复后应降至 0.01%。
    • 端到端延迟:Full Content 渲染完成时间与 SSE 结束时间的差值。

面试官视角:

  • 如果候选人提到 "Markdown 的 Ast Parser 在解析不完整流时的行为"(如 * 单独出现会吃掉后续字符),说明他真的做过流式渲染(加分,+20)。
  • 如果候选人只说 "使用 setInterval 轮询" 或者 "直接 innerHTML += chunk",说明他没做过复杂流式场景,不懂 XSS 风险和渲染性能(扣分,-20)。
  • 常见错误回答:"这是后端的问题,让后端发快点。"
  • 可以追问:"如果 SSE 连接断开了,前端怎么知道是发完了还是断网了?你的 HTTP 状态码或者事件监听是怎么设计的?"

Q2: [Title Agent 的多阶段编排与评分]

难度: Senior
领域: Agent架构 / Prompt工程
对应工作: 今天验收了 ai-developer-knowledge-hub 项目中的 Title Agent,该 Agent 需生成 3 个候选标题、评分并选出最佳标题。

题目:
你需要设计一个 Title Generation Agent,输入是一段长文本,输出是唯一的最佳标题。为了质量,不能只让 LLM 生成一个结果。你需要设计一个工作流:先生成 N 个候选,然后对 N 个候选评分,最后选出最高分。这里有三种架构方案,你会选哪种,为什么?
方案 A:Single Prompt,一次要求 LLM 输出 JSON [{"title": "...", "score": 10}, ...]
方案 B:ReAct 循环,第一步 Tool Call 生成候选,第二步 Tool Call 进行评分。
方案 C:DAG(有向无环图)编排,并行调用 3 个独立的 LLM 实例各生成 1 个标题,再聚合到一个 Judge Agent 评分。
请给出代码架构(伪代码),并分析在 Token 成本和响应延迟上的 Trade-off。

答案要点:

  1. 核心思路:
    选用 方案 C(DAG 编排)。因为生成标题是“发散性任务”,并行调用可以减少首字延迟且增加多样性;评分是“收敛性任务”,需要全局视角。A 方案容易导致评分注水或候选雷同,B 方案串行导致延迟叠加。

  2. 技术方案(以 LangChain/LangGraph 风格伪代码):

from typing import List

# 1. 并行生成节点
def generate_candidates(context: str) -> List[str]:
    # 通过 Promise.all 或 ThreadPool 并行调用 3 次,System Prompt 稍作区分(如不同温度或角色)
    prompts = [
        f"Generate a catchy title for: {context}",
        f"Generate a professional title for: {context}",
        f"Generate a keyword-focused title for: {context}"
    ]
    # 并行执行
    return [llm.invoke(p) for p in prompts]

# 2. 评分节点
def rate_titles(titles: List[str], context: str) -> dict:
    judge_prompt = f"""
    Context: {context}
    Candidates: {titles}
    Rate each candidate 1-10 based on relevance and click-through rate.
    Return JSON with the best title.
    """
    response = llm.invoke(judge_prompt)
    return parse_json(response) # {"best_title": "...", "reasoning": "..."}

# 3. 编排逻辑
def run_title_agent(context: str):
    # Step 1: 并行生成
    candidates = generate_candidates(context)

    # Step 2: 评分 (串行或并行)
    best_choice = rate_titles(candidates, context)

    return best_choice["best_title"]
Enter fullscreen mode Exit fullscreen mode
  1. 权衡分析:

    • 方案 A (Single Prompt): 成本最低(1次调用),但 LLM 对自己的作品评分通常有偏差,且很难输出结构化的对比分析。
    • 方案 B (ReAct): 逻辑清晰,但如果 N 很大,Latency = T_gen + T_rate + T_gen + T_rate... 线性增长不可接受。
    • 方案 C (DAG): Latency = Max(T_gen1, T_gen2, T_gen3) + T_rate。性能最优,质量最高(因为不同 Prompt 激发不同创造力)。成本略高(4次调用),但对于标题生成这种高频但低成本场景完全可接受。
  2. 反面教训:

    • 在实际开发 Title Agent 时,如果只用一个 Prompt 要求“生成3个并选最好的”,LLM 往往会偷懒,生成 3 个几乎同义的标题,导致选择无效。
    • 必须在 Prompt 中强制要求 JSON Schema,否则 Judge Agent 可能会返回自然语言,导致解析失败进入 Dead Loop。
  3. 量化指标:

    • 多样性得分: 3 个候选标题的编辑距离,平均应 > 30%。
    • 用户采纳率: 用户不修改直接发布的比例。

面试官视角:

  • 如果候选人提到 "Temperature 设置差异"(生成时用 0.7-1.0,评分时用 0.1),说明他懂 LLM 的参数控制(加分,+20)。
  • 如果候选人只选方案 A 并说“Prompt 写得好就行”,说明他缺乏工程化思维,忽略了 LLM 的概率特性(扣分,-10)。
  • 常见错误回答:“用 Map-Reduce。”(回答太泛,没有针对生成和评分的具体区别)。
  • 可以追问:“如果 3 个并行生成的标题都很烂,Judge Agent 必须选一个,怎么办?你的系统支持‘回炉重造’吗?”

Q3: [流式传输中的“第一行陷阱”与边界处理]

难度: Senior
领域: 生产化AI / 踩坑题
对应工作: 今天在修复打字机中断时,引入了一个新 Bug:修改导致只输出第一行。这是典型的“修了一个 Bug 引入两个 Bug”的场景。

题目:
在处理流式文本追加时,为了解决“文本过长导致渲染卡顿”,你决定在前端做一个优化:只在渲染前 100 个字符,后续丢弃(或者只保留前 3 行)。结果上线后,用户发现 AI 的回答永远只有半截话。请从代码层面分析,这种“截断逻辑”错在哪里?如果必须做“性能优化”(不能全量渲染长文本),正确的做法是什么?请写出修复后的代码逻辑。

答案要点:

  1. 核心思路:
    错误在于混淆了 “状态存储”“视图渲染”。状态必须永远完整,视图可以只显示一部分(如虚拟滚动或折叠)。但题目中描述的逻辑是在“追加阶段”就丢弃了数据,导致后续没有数据可追加。

  2. 技术方案:

// --- 错误做法 (今天的坑) ---
function onChunk(chunk) {
  // 致命错误:直接修改了需要持久化的 text 状态
  let currentText = getTextState();
  let newText = currentText + chunk;

  // 错误的截断逻辑:为了渲染快,把数据截断了,导致下次追加时 newText 永远只有第一行
  if (newText.includes('\n')) {
    newText = newText.split('\n')[0];
  }

  setTextState(newText);
}

// --- 正确做法 ---
let fullTextBuffer = ""; // 永久完整存储

function onChunk(chunk) {
  // 1. 追加逻辑:绝对完整
  fullTextBuffer += chunk;

  // 2. 渲染逻辑:可以只渲染一部分,但不能破坏 Buffer
  let displayText = fullTextBuffer;

  // 性能优化示例:如果是纯文本且超长,只渲染倒数 N 个字符(模拟打字机光标处)
  // 或者使用 CSS 虚拟列表
  /*
  if (fullTextBuffer.length > 5000) {
     displayText = "..." + fullTextBuffer.slice(-4000); // 视觉优化,不影响数据
  }
  */

  // 3. 状态更新
  updateUI(displayText); // 仅用于显示
}
Enter fullscreen mode Exit fullscreen mode
  1. 权衡分析:

    • 内存 vs 完整性: 保留 fullTextBuffer 会占用内存。但对于聊天气泡场景,单个文本极少超过 10k token,内存开销可忽略。
    • 如果必须截断: 只有在明确不需要历史记录的场景(如实时日志控制台)才在接收层截断。对于 AI 对话,必须保留全文以便用户复制、重新生成或总结。
  2. 反面教训:

    • 今天的回归: 为了解决“显示中断”,怀疑是“太长了”,所以加了 split('\n')[0]。实际上后端还在源源不断发第二行、第三行,但前端状态里永远只有第一行,新的 chunk 追加到第一行后面,变成了 Line1Line2Line3... 且没有换行符,看起来就是一串乱码或只有第一行。
    • 调试时必须确认“输入源”和“状态变量”的值,而不是只看 UI。
  3. 量化指标:

    • 无。此题考查的是逻辑正确性,而非性能指标。

面试官视角:

  • 如果候选人能立刻指出 “数据源与视图分离” 的原则,说明有扎实的架构基础(加分,+20)。
  • 如果候选人开始纠结“后端是不是发了换行符”,说明还没意识到是前端逻辑写死了(扣分,-10)。
  • 常见错误回答:“增加 buffer 大小。”(方向错了,buffer 再大,逻辑只要截断就没用)。
  • 可以追问:“如果用户在流式输出中途点击了‘停止生成’,你的状态机怎么处理?此时 Buffer 是不是完整的?”

Top comments (0)