feat: 实现流式输出功能 v0.1.0

- 创建 hxclaw 项目,基于 picoclaw 的 CLI 增强工具
- 实现流式输出,使用 fmt.Print + os.Stdout.Sync() 实时刷新
- 解决 onChunk 回调累积文本导致的重复输出问题
- 使用 strings.Builder 收集完整响应并保存到 session
- 添加讨论记录和更新日志文档
This commit is contained in:
2026-04-11 22:32:43 +08:00
commit d42c70f5ff
8 changed files with 1024 additions and 0 deletions

141
taolun.md Normal file
View File

@@ -0,0 +1,141 @@
# hxclaw 讨论记录
## 知识点汇总
### 1. hxclaw 是什么?
- hxclaw 是基于 picoclaw 的 CLI 增强工具
- 提供流式输出(替代原有的批量输出)
- 提供 Markdown 终端渲染(提升阅读体验)
- 定位: picoclaw 的变体,面向需要更好终端交互体验的用户
### 2. 为什么采用独立二进制而非插件?
- picoclaw 目前没有正式的插件系统
- 工具扩展通过 ToolRegistry 注册(如 spi.go, i2c.go
- 技能通过 SKILL.md 文件加载
- MCP 通过配置连接外部服务器
- 最小侵入方式:独立二进制 + 复用核心库
### 3. 如何复用 picoclaw 功能?
- 使用 Go 的 replace 机制在 go.mod 中声明依赖
- 开发时 replace 指向本地 picoclaw 目录
- 发布时 replace 指向 GitHub 具体版本(如 v0.2.4
- hxclaw 只需导入pkg/agent, pkg/providers, pkg/config, pkg/bus
### 4. hxclaw 架构设计
```
hxclaw/
├── cmd/hxclaw/ # CLI 入口(自己实现)
├── go.mod # 依赖配置
└── pkg/ # 空目录,全量复用 picoclaw
```
- 不依赖pkg/channels不需要消息通道、pkg/gateway不需要 HTTP 服务、web/(不需要网页)
- 依赖pkg/agent核心逻辑、pkg/providersLLM 调用、pkg/config配置加载
### 5. 流式输出原理
- picoclaw 的 providers 已支持 StreamingProvider 接口
- 接口定义ChatStream(ctx, messages, tools, model, options, onChunk)
- onChunk 是回调函数,每个 token 生成时调用
- CLI 层需要判断 provider 是否实现 StreamingProvider然后选择调用
### 6. Markdown 终端渲染
- 使用 charmbracelet 家族库
- lipgloss终端样式
- glow代码高亮
- 流程Markdown → ANSI 转义序列 → 终端显示
### 7. 部署方式
- 独立二进制 hxclaw与 picoclaw 二进制共存于同一目录
- 用户使用 `hxclaw` 命令调用增强版 CLI
- 配置文件复用 picoclaw 的 config.json位于 ~/.picoclaw/config.json
### 8. 版本同步策略
- 关键版本跟进(功能大版本更新时)
- 不需要每次 picoclaw 升级都同步
- 依赖版本在 go.mod 中声明,更新时修改 replace 目标版本即可
### 9. AgentRegistry 的正确使用方式
- `AgentRegistry` 负责管理多个 Agent 实例
- `GetDefaultAgent()` 获取默认的 Agent 实例
- `AgentInstance` 包含:
- `Provider` - LLM 提供者
- `ContextBuilder` - 消息构建器
- `Tools` - 工具注册表
- `Model` - 模型名称
- 注意:`AgentRegistry` 没有 `BuildMessages` 方法,该方法属于 `ContextBuilder`
### 10. ToolDefinitions 的获取方式
- 通过 `agentInstance.Tools.ToProviderDefs()` 获取
- 返回 `[]providers.ToolDefinition` 格式
- 该方法将工具注册表中的工具转换为 provider 可用的格式
### 11. 流式输出的实现问题与解决方案
#### 问题 1onChunk 回调接收累积文本
picoclaw 的 `StreamingProvider` 接口定义:
```go
onChunk func(accumulated string)
```
注释明确说明:"onChunk receives the accumulated text so far (not individual deltas)"。
这意味着每次回调时参数是完整的累积文本(如 "你好" → "你好!再次" → "你好!再次见到"),而不是增量。
#### 问题 2直接打印导致重复输出
如果直接打印 token
```go
func(token string) {
fmt.Print(token) // 打印累积文本!
}
```
会导致输出:
```
你好
你好!再次
你好!再次见到
...
```
#### 解决方案 1跟踪已打印长度
使用 `printedLen` 跟踪已打印的字符位置,只打印新增部分:
```go
var printedLen int
func(accumulated string) {
if len(accumulated) > printedLen {
fmt.Print(accumulated[printedLen:])
printedLen = len(accumulated)
}
}
```
#### 问题 3尝试使用 uilive 库
尝试使用 `github.com/gosuri/uilive` 库实现同行流动效果,但发现该库会覆盖每一行,只显示最后一行内容,不符合需求。
#### 最终解决方案:直接 Print + Sync
```go
fmt.Print(accumulated[printedLen:])
os.Stdout.Sync()
```
这样:
1. 字符串自然累积增长
2. 终端自动处理换行(满一行自动 wrap
3. 保留所有历史输出
4. 每次刷新缓冲区确保立即显示
这正是 ollama 等工具的流式输出效果。