feat(aibox): add DeepSeek reasoning mode support with tool calling on deepseek-v4-flash
- Add api.reasoning_enabled config (default false) to enable thinking mode - Uses deepseek-v4-flash native thinking capability (thinking param), supports tool calls while reasoning - Real-time reasoning content streaming via reasoning_start/reasoning/reasoning_end WS messages - Frontend shows reasoning in gray italic area, auto-folds long reasoning (>2000 chars) - Dynamic reasoning detection: shows reasoning even if model returns it without explicit config - Increase default API timeout to 120s for longer reasoning responses - Remove duplicate _add_assistant_message_with_reasoning method - Update docs: config example, message types table, v2.5 changelog
This commit is contained in:
1 parent
81ae84851d
commit
b79044d631
5 files changed
+131
-39
No files matched your search
@@ -1,9 +1,12 @@
|
||||
"""聊天视图:用户/AI/系统消息 + AI 回复流式 Markdown 渲染。
|
||||
|
||||
服务端协议(aibox/aibox.py output_callback 原样透传 msg_type):
|
||||
- chunk 增量片段(服务端 chunk_buffer 发送后即清零),直接 append
|
||||
- complete 收尾信号:chunk 流式是内容权威来源,complete 仅用于收尾;
|
||||
仅当流式完全丢帧(未建 widget 且无内容)时,用 full_text 补建
|
||||
- chunk 增量片段(正式回答内容),直接 append 到 AI 消息
|
||||
- reasoning_start 思考开始信号,创建思考区域
|
||||
- reasoning 思考内容增量片段,追加到思考区域
|
||||
- reasoning_end 思考结束信号
|
||||
- complete 收尾信号:chunk 流式是内容权威来源,complete 仅用于收尾;
|
||||
仅当流式完全丢帧(未建 widget 且无内容)时,用 full_text 补建
|
||||
"""
|
||||
from textual.containers import VerticalScroll
|
||||
from textual.widgets import Markdown, Static
|
||||
@@ -20,7 +23,9 @@ class ChatView(VerticalScroll):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._ai_widget: Markdown | None = None
|
||||
self._reasoning_widget: Static | None = None
|
||||
self._ai_text = ""
|
||||
self._reasoning_text = ""
|
||||
self._ai_finalized = True # 初始无活跃 AI 消息
|
||||
|
||||
@property
|
||||
@@ -49,6 +54,39 @@ class ChatView(VerticalScroll):
|
||||
await self.mount(self._ai_widget)
|
||||
self._anchor_bottom()
|
||||
|
||||
async def start_reasoning(self, hint: str = "🤔 思考中..."):
|
||||
"""开始展示思考过程区域。"""
|
||||
if self._reasoning_widget is not None:
|
||||
return
|
||||
self._reasoning_text = ""
|
||||
self._reasoning_widget = Static(hint + "\n", classes="msg-reasoning", markup=False)
|
||||
await self.mount(self._reasoning_widget)
|
||||
self._anchor_bottom()
|
||||
|
||||
async def append_reasoning(self, delta: str):
|
||||
"""追加思考内容增量。"""
|
||||
if not delta:
|
||||
return
|
||||
if self._reasoning_widget is None:
|
||||
await self.start_reasoning()
|
||||
self._reasoning_text += delta
|
||||
# 限制思考内容显示长度(最多显示最后 2000 字符,避免思考过长占满屏幕)
|
||||
display = self._reasoning_text
|
||||
if len(display) > 2000:
|
||||
display = "...(前略)\n" + display[-2000:]
|
||||
self._reasoning_widget.update(f"💭 思考中...\n{display}")
|
||||
self.call_after_refresh(self.scroll_end, animate=False)
|
||||
|
||||
async def end_reasoning(self):
|
||||
"""结束思考区域,更新提示文字。"""
|
||||
if self._reasoning_widget is not None and self._reasoning_text:
|
||||
# 思考结束后保持展示,但更新标签
|
||||
display = self._reasoning_text
|
||||
if len(display) > 2000:
|
||||
display = "...(前略)\n" + display[-2000:]
|
||||
self._reasoning_widget.update(f"💭 思考过程({len(self._reasoning_text)}字符)\n{display}\n{'─' * 30}")
|
||||
# 思考区域保留显示,不删除
|
||||
|
||||
async def append_ai(self, delta: str):
|
||||
"""追加增量片段(服务端 chunk 为增量语义,直接拼接)。"""
|
||||
if not delta:
|
||||
@@ -97,12 +135,16 @@ class ChatView(VerticalScroll):
|
||||
self._anchor_bottom()
|
||||
|
||||
self._ai_text = ""
|
||||
self._reasoning_widget = None
|
||||
self._reasoning_text = ""
|
||||
self._ai_finalized = True
|
||||
self.call_after_refresh(self.scroll_end, animate=False)
|
||||
|
||||
async def reset(self):
|
||||
"""清空聊天区(/clear)。"""
|
||||
self._ai_widget = None
|
||||
self._reasoning_widget = None
|
||||
self._ai_text = ""
|
||||
self._reasoning_text = ""
|
||||
self._ai_finalized = True
|
||||
await self.remove_children()
|
||||
Reference in new issue
Block a user