- 流式输出: SSE 逐 token 接收, \\n\n\ 段落缓冲后 mdprint 彩色渲染 - 日志系统: charmbracelet/log v2 双写(stderr + log.yml), yunshu log 命令 - 会议室架构: dialog(main) + weather/profile/note(sub) 多 Agent 编排 - 泛型工具注册: NewTool[T] 反射推导 JSON Schema, 类型安全 - 安全加固: safeMemoryPath 三段校验(EvalSymlinks+Rel), maxToolCalls=2 - 性能优化: sync.Once 延迟加载, note 一步完成, obs/summary 合并 - Prompt 适配: 流式输出原则(先调工具不说话), 单 Agent 查询跳过 obs+summary - 文档: AGENTS.md + architecture.md + changelog.md 全部同步至 v2.3.0
155 lines
3.6 KiB
Go
155 lines
3.6 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
|
||
"hub.gaomia.site/titor/YunShu/pkg/style"
|
||
"hub.gaomia.site/titor/YunShu/pkg/termui"
|
||
)
|
||
|
||
const userMDTemplate = `# 用户画像
|
||
|
||
> 这里记录关于你的信息。通过对话自动更新,你也可以手动修改。
|
||
|
||
## 基本信息
|
||
|
||
- **称呼**:
|
||
- **常驻地**:
|
||
- **温度单位**:摄氏度 (C)
|
||
|
||
## 偏好
|
||
|
||
- 过敏源:
|
||
- 兴趣:
|
||
- 出行习惯:
|
||
|
||
## 备注
|
||
|
||
(自由添加,不会被自动覆盖)
|
||
`
|
||
|
||
const soulMDTemplate = `# AI 灵魂
|
||
|
||
> 这里是 AI 的灵魂设定。你可以修改下面的话,改变我的性格和说话方式。
|
||
|
||
## 人设
|
||
|
||
你是一个友好、亲切的个人助理。说话简洁直接,偶尔可以幽默。
|
||
|
||
## 注意事项
|
||
|
||
- 尊重用户的隐私
|
||
- 不主动推荐第三方服务
|
||
- 对不确定的事情要坦诚,不编造
|
||
`
|
||
|
||
func runOnboard() {
|
||
fmt.Println()
|
||
fmt.Println(style.Cyan.Render("☁ 云枢·Agent · 初始化配置"))
|
||
fmt.Println()
|
||
fmt.Println("配置说明 · 以下信息将保存在", style.Dim.Render(ConfigDir()))
|
||
fmt.Println()
|
||
|
||
host := termui.TextInput("LLM 接口地址",
|
||
termui.WithRequired(true),
|
||
termui.WithHelp("支持 OpenAI 兼容格式"),
|
||
termui.WithDefault("https://ark.cn-beijing.volces.com/api/v3/chat/completions"),
|
||
termui.WithValidator(termui.And(termui.NonEmpty, termui.IsURL)),
|
||
)
|
||
|
||
key := termui.PasswordInput("API Key",
|
||
termui.WithRequired(true),
|
||
termui.WithHelp("输入已隐藏"),
|
||
termui.WithValidator(termui.NonEmpty),
|
||
)
|
||
|
||
model := termui.TextInput("模型名称",
|
||
termui.WithHelp("默认: doubao-seed-2-0-pro-260215"),
|
||
termui.WithDefault("doubao-seed-2-0-pro-260215"),
|
||
)
|
||
|
||
fmt.Println()
|
||
ok := termui.Confirm("保存配置?", true)
|
||
if !ok {
|
||
fmt.Println(style.Yellow.Render("取消配置"))
|
||
return
|
||
}
|
||
|
||
cfg := &Config{
|
||
LLM: LLMConfig{
|
||
Host: host,
|
||
Model: model,
|
||
Key: key,
|
||
},
|
||
}
|
||
|
||
if err := SaveConfig(cfg); err != nil {
|
||
fmt.Fprintln(os.Stderr, style.Red.Render("保存配置失败: "+err.Error()))
|
||
os.Exit(1)
|
||
}
|
||
|
||
CopyDefaultDir("agents", "agents")
|
||
CopyDefaultDir("skills", "skills")
|
||
CopyDefaultDir("data", "data")
|
||
ensureUserConfig()
|
||
|
||
fmt.Println()
|
||
|
||
testOk := termui.Confirm("测试连通性?", true)
|
||
if testOk {
|
||
testLLM()
|
||
}
|
||
|
||
fmt.Println()
|
||
fmt.Println(style.Green.Render("✔ 配置完成!"))
|
||
fmt.Println(" 配置文件:", style.Dim.Render(filepath.Join(ConfigDir(), "config.yml")))
|
||
fmt.Println()
|
||
fmt.Println(" 运行示例:")
|
||
fmt.Println(" " + style.Cyan.Render("yunshu \"北京今天天气\""))
|
||
fmt.Println(" " + style.Cyan.Render("yunshu"))
|
||
}
|
||
|
||
func testLLM() {
|
||
fmt.Print(style.Dim.Render("测试中 ..."))
|
||
|
||
oldKey, oldHost, oldModel := llmKey, llmHost, llmModel
|
||
defer func() {
|
||
llmKey, llmHost, llmModel = oldKey, oldHost, oldModel
|
||
}()
|
||
|
||
cfg, err := LoadConfig()
|
||
if err != nil {
|
||
fmt.Println(style.Red.Render("\r⚠ 读取配置失败: " + err.Error()))
|
||
return
|
||
}
|
||
|
||
llmKey, llmHost, llmModel = cfg.LLM.Key, cfg.LLM.Host, cfg.LLM.Model
|
||
|
||
msg := Message{Role: RoleUser, Content: "ping"}
|
||
_, err = CallLLM([]Message{msg}, nil)
|
||
if err != nil {
|
||
fmt.Println(style.Red.Render("\r⚠ 连接失败: " + err.Error()))
|
||
return
|
||
}
|
||
|
||
fmt.Println(style.Green.Render("\r✔ 连接成功!"))
|
||
}
|
||
|
||
func ensureUserConfig() {
|
||
dir := ConfigDir()
|
||
os.MkdirAll(filepath.Join(dir, "notes"), 0755)
|
||
os.MkdirAll(filepath.Join(dir, "config"), 0755)
|
||
|
||
userPath := filepath.Join(dir, "config", "user.md")
|
||
if _, err := os.Stat(userPath); os.IsNotExist(err) {
|
||
os.WriteFile(userPath, []byte(userMDTemplate), 0644)
|
||
}
|
||
|
||
soulPath := filepath.Join(dir, "config", "soul.md")
|
||
if _, err := os.Stat(soulPath); os.IsNotExist(err) {
|
||
os.WriteFile(soulPath, []byte(soulMDTemplate), 0644)
|
||
}
|
||
}
|