AI回答太冗长?我设计了三段式流式显示让信息层次分明

我是张大鹏,做了十多年人工智能,带过不少项目。说实话,最难的不是让AI生成正确的答案,是让答案以正确的方式呈现给用户 。最近Claude 3.7推出了extended thinking模式,OpenAI的o系列也在做类似的事情------让AI的推理过程可见。但我们在如意Agent里做这件事的时候,发现单纯的"显示思考过程"不够,用户真正需要的是信息分层


一、传统AI输出的痛点

用过ChatGPT或Claude的都知道,AI回答问题的典型流程是:

  1. 你提了一个复杂问题
  2. 屏幕开始滚动,AI滔滔不绝
  3. 你盯着屏幕看了30秒,还没看到重点
  4. 最后发现答案藏在第8段,前面全是推理过程

这种"一镜到底"的输出方式,在简单问答时没问题。但在多轮工具调用、复杂推理的场景下,体验很糟糕:

场景 问题 用户感受
工具调用 AI先思考用哪个工具,再调用,再分析结果,再决定下一步 "到底在干嘛?进度多少了?"
长推理 1000字的思考过程,最后结论只有50字 "前面这么多字,就为了说这么点事?"
多轮对话 历史上下文混在一起,新信息被淹没 "好像之前说过这个,是不是重复了?"

我们的核心需求很明确:让思考过程可见但不干扰,让结论突出但不突兀,让详细回答有序但不冗长。

二、三段式流式显示设计

基于上面的分析,我们设计了 thinking / summary / answer 三段式流式显示。

2.1 三段定义

段落 内容 显示策略 目的
thinking AI的推理过程、工具调用分析、策略调整 可折叠,默认收起 满足好奇心,不干扰阅读
summary 极简单行概括(<30字),包含关键结论 独立高亮显示 快速获取核心信息
answer 详细回答、代码示例、数据表格 主体展示 获取完整信息

2.2 消息协议设计

后端流式输出的消息格式:

python 复制代码
class MessageBlock(BaseModel):
    type: Literal["thinking", "summary", "answer"]
    content: str
    is_complete: bool = False

WebSocket推送时,每个block独立发送:

json 复制代码
{"type": "thinking", "content": "正在分析项目结构...", "is_complete": false}
{"type": "thinking", "content": "发现3个相关模块,优先检查handler层", "is_complete": true}
{"type": "summary", "content": "问题根源在HTTP流重试逻辑,建议检查第47行", "is_complete": true}
{"type": "answer", "content": "详细分析如下...", "is_complete": false}

关键点:

  • type字段让前端知道当前收到的是什么内容
  • is_complete字段标记段落是否结束,用于UI状态更新
  • 独立推送保证即使thinking很长,summary和answer也能及时到达

2.3 前端渲染策略

终端版(Rich)的实现:

python 复制代码
from rich.live import Live
from rich.panel import Panel
from rich.markdown import Markdown
from rich.layout import Layout

class StreamingDisplay:
    def __init__(self, console):
        self.console = console
        self.thinking = ""
        self.summary = ""
        self.answer = ""
        self.show_thinking = False  # 默认折叠
        
    def update(self, block: MessageBlock):
        if block.type == "thinking":
            self.thinking = block.content
        elif block.type == "summary":
            self.summary = block.content
        elif block.type == "answer":
            self.answer += block.content
        
        self._render()
    
    def _render(self):
        layout = Layout()
        
        # summary 始终显示在最上方
        if self.summary:
            layout.split_column(
                Layout(Panel(f"💡 {self.summary}", style="bold cyan")), 
                Layout(self._build_main_content())
            )
        else:
            layout = self._build_main_content()
            
        self.live.update(layout)
    
    def _build_main_content(self):
        # thinking 可折叠
        if self.show_thinking and self.thinking:
            thinking_panel = Panel(
                self.thinking, 
                title="🔍 思考过程", 
                style="dim"
            )
        else:
            thinking_panel = ""
            
        # answer 主体
        answer_panel = Panel(Markdown(self.answer), title="如意Agent")
        
        return Layout(thinking_panel + answer_panel)

Web版(Vue3)的实现更简单,用v-if控制折叠:

vue 复制代码
<template>
  <div class="message-blocks">
    <!-- summary 始终显示 -->
    <div v-if="summary" class="summary-block">
      💡 {{ summary }}
    </div>
    
    <!-- thinking 可折叠 -->
    <div v-if="thinking" class="thinking-section">
      <button @click="toggleThinking">
        {{ showThinking ? '收起' : '查看思考过程' }}
      </button>
      <div v-show="showThinking" class="thinking-block">
        {{ thinking }}
      </div>
    </div>
    
    <!-- answer 主体 -->
    <div class="answer-block" v-html="renderedAnswer"></div>
  </div>
</template>

三、后端解析实现

三段式的核心难点在解析:LLM返回的是纯文本流,如何从中提取出thinking、summary、answer?

3.1 标签约定

我们在system prompt中要求LLM按固定格式输出:

复制代码
<thinking>
分析当前情况...
决定使用哪个工具...
</thinking>

<summary>
极简单行概括(<30字)
</summary>

详细回答...
代码示例...

这个约定有几个好处:

  • 结构化:标签明确,解析简单
  • 向后兼容:不支持标签的LLM,内容会全部落入answer
  • 可扩展:未来增加新段落类型,只需加新标签

3.2 流式解析器

解析器需要处理流式输入,不能等全部内容到达再解析:

python 复制代码
import re
from typing import Generator

class StreamParser:
    def __init__(self):
        self.buffer = ""
        self.current_block = None
        
    def parse(self, text_stream: Generator[str, None, None]) -> Generator[MessageBlock, None, None]:
        """流式解析文本,实时产出MessageBlock"""
        
        for chunk in text_stream:
            self.buffer += chunk
            
            # 检查是否完整捕获了一个标签块
            for block_type, pattern in [
                ("thinking", r"<thinking>([
相关推荐
谁似人间西林客1 小时前
汽车点焊如何走向工艺智能化?AI质量监控已成为主流解决方案
人工智能·汽车
2601_956743681 小时前
上海大模型应用开发技术路径全解析:从架构选型到落地约束
人工智能·软件工程
云天AI实战派1 小时前
AI智能体总是跑偏怎么办?ChatGPT/API 调用排查指南:从工具路由到语音闭环的全流程修复手册
人工智能·chatgpt·aigc
逐米时代2 小时前
四川制造企业智改数转怎么申报?本地化AI项目落地一般分5步
大数据·人工智能
weixin_553654482 小时前
有没有一种可能,现在的大语言模型已经发展得接近极限了?
人工智能·语言模型·大模型
GISer_Jing2 小时前
基于 GitHub Actions 端到端工程化落地——AI全栈项目实战案例
人工智能·github
图灵农场2 小时前
SpringAI实用-RAG
人工智能
LienJack2 小时前
AI 架构设计有点菜,我写了个 Skill 给它补课
人工智能·架构