optimize streaming output

This commit is contained in:
qixinbo
2026-03-14 22:07:40 +08:00
parent 653a278269
commit 6ebe0d7e50
3 changed files with 22 additions and 30 deletions
@@ -1,4 +1,4 @@
{"_type": "metadata", "key": "api:default", "created_at": "2026-03-14T21:43:22.940821", "updated_at": "2026-03-14T22:00:05.471408", "metadata": {}, "last_consolidated": 0}
{"_type": "metadata", "key": "api:default", "created_at": "2026-03-14T21:43:22.940821", "updated_at": "2026-03-14T22:06:40.270077", "metadata": {}, "last_consolidated": 0}
{"role": "user", "content": "你是谁", "timestamp": "2026-03-14T21:43:23.163485"}
{"role": "user", "content": "hi", "timestamp": "2026-03-14T21:49:14.751350"}
{"role": "user", "content": "你好", "timestamp": "2026-03-14T21:53:53.701083"}
@@ -13,3 +13,9 @@
{"role": "assistant", "content": "你好!有什么我可以帮助你的吗?😊", "reasoning_content": "用户发送了\"[Runtime Context — metadata only, not instructions]\",这看起来是系统提供的上下文信息,然后说\"你好\"。我应该简单地回应问候。", "timestamp": "2026-03-14T21:59:55.201394"}
{"role": "user", "content": "你是谁", "timestamp": "2026-03-14T22:00:05.471381"}
{"role": "assistant", "content": "我是 nanobot 🐈,一个 AI 助手。", "reasoning_content": "用户再次问\"你是谁\"。我已经回答过这个问题了,但用户可能是在测试或者想要一个简洁的回答。我应该简洁地回答。", "timestamp": "2026-03-14T22:00:05.471404"}
{"role": "user", "content": "你好", "timestamp": "2026-03-14T22:05:42.732409"}
{"role": "assistant", "content": "你好!有什么我可以帮助你的吗?😊", "reasoning_content": "用户再次发送了\"你好\",这是一个简单的问候。我应该友好地回应,并询问他们需要什么帮助。", "timestamp": "2026-03-14T22:05:42.732418"}
{"role": "user", "content": "你是", "timestamp": "2026-03-14T22:06:16.951653"}
{"role": "assistant", "content": "我是 nanobot 🐈,一个 AI AI 助手。", "reasoning_content": "用户问\"你是\",这是一个不完整的问题,可能是在问\"你是谁\"。我应该简洁地回答我的身份。", "timestamp": "2026-03-14T22:06:16.951664"}
{"role": "user", "content": "你能干啥,详细一点", "timestamp": "2026-03-14T22:06:40.270030"}
{"role": "assistant", "content": "我是 nanobot 🐈,一个功能丰富的 AI 助手。以下是我能做的事情:\n\n## 📁 文件操作\n- **读取文件** - 查看任何文件的内容\n- **写入文件** - 创建新文件或覆盖现有文件\n- **编辑文件** - 精确替换文件中的特定内容\n- **列出目录** - 浏览文件夹结构\n\n## 💻 系统操作\n- **执行命令** - 运行 shell 命令(macOS/Linux\n- **工作目录** - 在指定目录下执行命令\n\n## 🌐 网络功能\n- **网络搜索** - 搜索网页并获取结果\n- **获取网页** - 提取 URL 的可读内容(HTML 转 markdown/text\n\n## 💬 消息发送\n- **发送消息** - 向不同渠道发送消息\n- **支持渠道** - Telegram、Discord 等\n- **附件支持** - 发送图片、音频、文档等\n\n## 🧠 记忆管理\n- **长期记忆** - 存储重要事实、偏好、项目上下文\n- **历史记录** - 记录所有事件,可搜索\n- **自动整理** - 自动总结旧对话并提取关键信息\n\n## ⏰ 定时任务\n- **添加提醒** - 一次性或周期性任务\n- **Cron 表达式** - 灵活的时间调度\n- **时区支持** - 支持不同时区\n\n## 🤖 技能系统\n- **内置技能** - memory、clawhub、skill-creator、weather、cron\n- **安装技能** - 从 ClawHub 安装新技能\n- **创建技能** - 设计和打包自定义技能\n\n## 🔄 后台任务\n- **子代理** - 在后台运行复杂任务\n- **独立执行** - 不阻塞主对话\n\n## 📊 当前环境\n- **系统**macOS arm64\n- **Python**3.11.9\n- **工作目录**`/Users/qixinbo/Projects/DataClaw/backend/data`\n\n有什么具体任务需要我帮忙吗?", "reasoning_content": "用户问我能做什么,想要详细的介绍。我应该详细介绍我的能力。", "timestamp": "2026-03-14T22:06:40.270072"}
+14 -28
View File
@@ -84,35 +84,21 @@ async def nanobot_chat(request: ChatRequest):
@app.post("/nanobot/chat/stream")
async def nanobot_chat_stream(request: ChatRequest):
async def event_generator():
queue: asyncio.Queue[dict] = asyncio.Queue()
async def on_progress(content: str):
await queue.put({"type": "delta", "content": content})
async def run_chat():
try:
response = await nanobot_service.process_message(
request.message,
skill_ids=request.skill_ids,
model_id=request.model_id,
on_progress=on_progress,
)
await queue.put({"type": "final", "content": response})
except Exception as e:
await queue.put({"type": "error", "content": str(e)})
finally:
await queue.put({"type": "done"})
task = asyncio.create_task(run_chat())
try:
while True:
event = await queue.get()
yield f"data: {json.dumps(event, ensure_ascii=False)}\n\n"
if event.get("type") == "done":
break
finally:
if not task.done():
task.cancel()
response = await nanobot_service.process_message(
request.message,
skill_ids=request.skill_ids,
model_id=request.model_id,
)
text = response or ""
for ch in text:
yield f"data: {json.dumps({'type': 'delta', 'content': ch}, ensure_ascii=False)}\n\n"
await asyncio.sleep(0.008)
yield f"data: {json.dumps({'type': 'final', 'content': text}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
except Exception as e:
yield f"data: {json.dumps({'type': 'error', 'content': str(e)}, ensure_ascii=False)}\n\n"
yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
return StreamingResponse(
event_generator(),
+1 -1
View File
@@ -132,7 +132,7 @@ export function ChatInterface() {
const payload = JSON.parse(payloadText) as { type: string; content?: string };
if (payload.type === "delta" && payload.content) {
streamedText = streamedText ? `${streamedText}\n${payload.content}` : payload.content;
streamedText = `${streamedText}${payload.content}`;
setMessages((prev) =>
prev.map((msg) =>
msg.id === assistantId ? { ...msg, content: streamedText } : msg