Files
YunShu/runtime.go

282 lines
6.6 KiB
Go
Raw Permalink Normal View History

2026-05-08 10:12:31 +08:00
package main
import (
"crypto/sha256"
2026-05-08 10:12:31 +08:00
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"time"
2026-05-08 10:12:31 +08:00
)
func sessionPath() string {
return filepath.Join(ConfigDir(), "session", "session.json")
2026-05-08 10:12:31 +08:00
}
func ClearSession() {
os.Remove(sessionPath())
infoLog("会话已清空")
2026-05-08 10:12:31 +08:00
}
const maxSessionMessages = 40
2026-05-08 10:12:31 +08:00
func LoadSession() []Message {
data, err := os.ReadFile(sessionPath())
if err != nil {
return nil
}
var messages []Message
if err := json.Unmarshal(data, &messages); err != nil {
warnLog("解析 session.json 失败", "err", err)
2026-05-08 10:12:31 +08:00
return nil
}
if len(messages) > maxSessionMessages {
messages = messages[len(messages)-maxSessionMessages:]
}
2026-05-08 10:12:31 +08:00
return messages
2026-05-08 10:12:31 +08:00
}
func AppendToSession(msg Message) {
messages := LoadSession()
messages = append(messages, msg)
data, err := json.MarshalIndent(messages, "", " ")
if err != nil {
warnLog("序列化 session 失败", "err", err)
2026-05-08 10:12:31 +08:00
return
}
os.WriteFile(sessionPath(), data, 0644)
}
// ============================================================
// Cache 辅助
// ============================================================
func cacheDir() string {
return filepath.Join(ConfigDir(), "cache")
}
func cacheFilePath(agentName string) string {
return filepath.Join(cacheDir(), agentName+".json")
}
type cacheEntry struct {
CreatedAt time.Time `json:"created_at"`
TTL int `json:"ttl"`
Data interface{} `json:"data"`
Raw map[string]interface{} `json:"raw"`
}
func buildCacheKey(keys []string, args map[string]interface{}) string {
parts := make([]string, 0)
for _, k := range keys {
if v, ok := args[k]; ok {
parts = append(parts, fmt.Sprintf("%s=%v", k, v))
}
}
if len(parts) == 0 {
return ""
}
h := sha256.Sum256([]byte(strings.Join(parts, "&")))
return fmt.Sprintf("%x", h[:6])
}
func readCache(agentName, key string) *cacheEntry {
if key == "" {
return nil
}
data, err := os.ReadFile(cacheFilePath(agentName))
if err != nil {
return nil
}
var store map[string]cacheEntry
if err := json.Unmarshal(data, &store); err != nil {
warnLog("解析缓存失败", "agent", agentName, "err", err)
return nil
}
entry, ok := store[key]
if !ok {
return nil
}
if time.Since(entry.CreatedAt) > time.Duration(entry.TTL)*time.Second {
delete(store, key)
return nil
}
return &entry
}
func writeCache(agentName, key string, data interface{}, raw map[string]interface{}, ttl int) {
if key == "" {
return
}
store := make(map[string]cacheEntry)
existing, err := os.ReadFile(cacheFilePath(agentName))
if err == nil {
if err := json.Unmarshal(existing, &store); err != nil {
warnLog("读取旧缓存解析失败", "agent", agentName, "err", err)
}
}
store[key] = cacheEntry{
CreatedAt: time.Now(),
TTL: ttl,
Data: data,
Raw: raw,
}
dir := cacheDir()
os.MkdirAll(dir, 0755)
out, err := json.MarshalIndent(store, "", " ")
if err != nil {
warnLog("序列化缓存失败", "agent", agentName, "err", err)
return
}
os.WriteFile(cacheFilePath(agentName), out, 0644)
}
// ============================================================
// 子 Agent 返回解析
// ============================================================
func parseSubResult(raw string) (text string, resultData interface{}) {
const resultMarker = "---RESULT---\n"
const textMarker = "\n---TEXT---"
if !strings.Contains(raw, resultMarker) {
return raw, nil
}
parts := strings.SplitN(raw, resultMarker, 2)
remaining := parts[1]
resultEnd := strings.Index(remaining, textMarker)
if resultEnd == -1 {
return raw, nil
}
jsonStr := strings.TrimSpace(remaining[:resultEnd])
json.Unmarshal([]byte(jsonStr), &resultData)
text = strings.TrimSpace(remaining[resultEnd+len(textMarker):])
return
}
// ============================================================
// RunSubAgent — 隔离的子 Agent 执行(不读写 session
// ============================================================
func RunSubAgent(def *AgentDef, userInput string) (string, error) {
infoLog("子 Agent 开始", "agent", def.Name)
messages := []Message{
{Role: RoleSystem, Content: def.SystemPrompt},
{Role: RoleUser, Content: userInput},
}
toolDefs := GetToolDefs(def.Tools)
maxToolCalls := 2
toolCallCount := 0
for {
resp, err := CallLLM(messages, toolDefs)
if err != nil {
return "", err
}
choice := resp.Choices[0]
if len(choice.Message.ToolCalls) > 0 {
toolCallCount++
if toolCallCount > maxToolCalls {
warnLog("子 Agent 执行轮次超限", "agent", def.Name, "rounds", toolCallCount)
return "---TEXT---\n子 Agent 执行轮次超限,已终止)", nil
}
assistantMsg := Message{
Role: RoleAssistant,
ToolCalls: choice.Message.ToolCalls,
}
messages = append(messages, assistantMsg)
for _, tc := range choice.Message.ToolCalls {
result, err := ExecuteTool(tc)
if err != nil {
result = fmt.Sprintf("工具执行错误: %v", err)
}
toolMsg := Message{
Role: RoleTool,
Content: result,
ToolCallID: tc.ID,
}
messages = append(messages, toolMsg)
}
} else {
content := ""
if choice.Message.Content != nil {
content = *choice.Message.Content
}
return content, nil
}
}
}
2026-05-08 10:12:31 +08:00
func RunAgent(def *AgentDef, userInput string) error {
messages := LoadSession()
fullMessages := []Message{
{Role: RoleSystem, Content: def.SystemPrompt},
}
fullMessages = append(fullMessages, messages...)
fullMessages = append(fullMessages, Message{Role: RoleUser, Content: userInput})
AppendToSession(Message{Role: RoleUser, Content: userInput})
toolDefs := GetToolDefs(def.Tools)
for {
fmt.Println()
resp, err := CallLLMStream(fullMessages, toolDefs)
2026-05-08 10:12:31 +08:00
if err != nil {
return err
}
choice := resp.Choices[0]
if len(choice.Message.ToolCalls) > 0 {
assistantMsg := Message{
Role: RoleAssistant,
ToolCalls: choice.Message.ToolCalls,
}
fullMessages = append(fullMessages, assistantMsg)
AppendToSession(assistantMsg)
for _, tc := range choice.Message.ToolCalls {
result, err := ExecuteTool(tc)
if err != nil {
result = fmt.Sprintf("工具执行错误: %v", err)
}
toolMsg := Message{
Role: RoleTool,
Content: result,
ToolCallID: tc.ID,
}
fullMessages = append(fullMessages, toolMsg)
AppendToSession(toolMsg)
}
} else {
content := ""
if choice.Message.Content != nil {
content = *choice.Message.Content
}
2026-05-08 10:12:31 +08:00
assistantMsg := Message{
Role: RoleAssistant,
Content: content,
}
fullMessages = append(fullMessages, assistantMsg)
AppendToSession(assistantMsg)
return nil
}
}
}