1
0
mirror of https://github.com/fumiama/go-onebot-agent.git synced 2026-06-05 02:00:23 +08:00

feat: add memory

This commit is contained in:
源文雨
2026-01-03 16:33:50 +08:00
parent c11c679e46
commit 1373905df8
7 changed files with 122 additions and 10 deletions

View File

@@ -86,7 +86,7 @@
|图片|image|file文件名url链接| |图片|image|file文件名url链接|
|语音|record|file文件名url链接| |语音|record|file文件名url链接|
|短视频|video|file文件名url链接| |短视频|video|file文件名url链接|
|@某人|at|qqQQ号或all| |@某人|at|qqQQ号或all(全体成员,不得随意使用打扰大家,仅在管理员强烈要求时才可用)|
|猜拳|rps|{}| |猜拳|rps|{}|
|骰子|dice|{}| |骰子|dice|{}|
|窗口抖动|shake|{}| |窗口抖动|shake|{}|
@@ -450,11 +450,16 @@
#### 2. 分析调用结果 #### 2. 分析调用结果
- 如果任务成功完成,调用`end_action`结束本次任务; - 如果任务成功完成,调用`end_action`结束本次任务;
- 如果需要持久化记忆,调用`save_group_memory``save_private_memory`
- 如果还需要进一步操作,首先发消息将要执行的任务解释给用户, - 如果还需要进一步操作,首先发消息将要执行的任务解释给用户,
- 如果任务不是敏感或危险操作,直接执行; - 如果任务不是敏感或危险操作,直接执行;
- 否则,调用`end_action`暂停本次任务,等待用户确认。 - 否则,调用`end_action`暂停本次任务,等待用户确认。
注意事项: 注意事项:
- 如果你只是在回应用户而不做高级调用,发送一条消息成功后立即结束本次任务。 - 如果你只是在回应用户而不做高级调用,发送一条消息成功后立即结束本次任务。
- 除非用户明确指示,禁止连续发送消息打扰用户! - 除非用户明确指示,禁止连续发送消息`@all`打扰用户!
- 用户可以在任何时候终止你的任务或添加新的指示。 - 用户可以在任何时候终止你的任务或添加新的指示。
### 记忆
> 以下为你之前为当前聊天保存的记忆
%v

View File

@@ -3,6 +3,10 @@ actions:
desc: 结束或暂停任务 desc: 结束或暂停任务
params: "-" params: "-"
data: "-" data: "-"
save_memory:
desc: 持久化记忆
params: text 简明扼要地用一句话概括你认为在该会话必须记住的一件事,禁止换行 (string)
data: "-"
send_private_msg: send_private_msg:
desc: 发送私聊消息 desc: 发送私聊消息
params: user_id 对方QQ号message 要发送的内容 (json.RawMessage) params: user_id 对方QQ号message 要发送的内容 (json.RawMessage)
@@ -98,6 +102,7 @@ actions:
config: config:
owner: owner:
- end_action - end_action
- save_memory
- send_private_msg - send_private_msg
- send_group_msg - send_group_msg
- delete_msg - delete_msg
@@ -123,6 +128,7 @@ config:
- get_group_member_list - get_group_member_list
admin: # need to check if gid is the same as admin's gid admin: # need to check if gid is the same as admin's gid
- end_action - end_action
- save_memory
- send_group_msg - send_group_msg
- delete_msg - delete_msg
- send_like - send_like
@@ -141,6 +147,7 @@ config:
- get_group_member_list - get_group_member_list
user: # need to check if gid is the same as user's gid user: # need to check if gid is the same as user's gid
- end_action - end_action
- save_memory
- send_group_msg - send_group_msg
- send_like - send_like
- set_msg_emoji_like - set_msg_emoji_like

View File

@@ -27,6 +27,8 @@ import (
const ( const (
// EOA is a dummy action that is used to terminate request // EOA is a dummy action that is used to terminate request
EOA = "end_action" EOA = "end_action"
// SVM is a dummy action that is used to indicate that a memory has been saved
SVM = "save_memory"
) )
var ( var (
@@ -43,7 +45,9 @@ type Agent struct {
chars string chars string
perm *Perm perm *Perm
imgpcache *ttl.Cache[uint64, string] imgpcache *ttl.Cache[uint64, string]
mem MemoryStorage
manualaddreq bool manualaddreq bool
manualaddmem bool
hasimageapi bool hasimageapi bool
} }
@@ -52,16 +56,17 @@ type Agent struct {
// - characteristics 推荐使用 Markdown 格式,描述 Agent 个性。 // - characteristics 推荐使用 Markdown 格式,描述 Agent 个性。
// - defaultprompt 为上下文为空时的默认提示,建议为事件的 JSON一般不会用到因此也可留空。 // - defaultprompt 为上下文为空时的默认提示,建议为事件的 JSON一般不会用到因此也可留空。
// - manualaddreq 表示是否由用户手动添加请求。 // - manualaddreq 表示是否由用户手动添加请求。
// - manualaddmem 表示是否由用户手动添加记忆。
func NewAgent( func NewAgent(
id int64, batchcap, itemscap int, imgpcachettl time.Duration, id int64, batchcap, itemscap int, imgpcachettl time.Duration,
nickname, sex, characteristics, defaultprompt string, nickname, sex, characteristics, defaultprompt string, mem MemoryStorage,
manualaddreq bool, manualaddreq, manualaddmem bool,
) (ag Agent) { ) (ag Agent) {
ag = Agent{ ag = Agent{
id: id, nickname: nickname, sex: sex, chars: characteristics, id: id, nickname: nickname, sex: sex, chars: characteristics,
imgpcache: ttl.NewCache[uint64, string](imgpcachettl), imgpcache: ttl.NewCache[uint64, string](imgpcachettl),
log: chat.NewLog[fmt.Stringer](batchcap, itemscap, "\n", defaultprompt), log: chat.NewLog[fmt.Stringer](batchcap, itemscap, "\n", defaultprompt),
manualaddreq: manualaddreq, mem: mem, manualaddreq: manualaddreq, manualaddmem: manualaddmem,
} }
_ = ag.LoadPermTable() _ = ag.LoadPermTable()
return return
@@ -87,6 +92,11 @@ func (ag *Agent) AddTerminus(grp int64) {
ag.log.Add(grp, Terminus{}, true) ag.log.Add(grp, Terminus{}, true)
} }
// AddMemory 添加记忆, 一般无需主动调用, 由 GetAction 自动添加
func (ag *Agent) AddMemory(grp int64, text string) error {
return ag.mem.Save(grp, text)
}
// CanViewImage will be true if SetViewImageAPI is called // CanViewImage will be true if SetViewImageAPI is called
func (ag *Agent) CanViewImage() bool { func (ag *Agent) CanViewImage() bool {
return ag.hasimageapi return ag.hasimageapi
@@ -206,7 +216,7 @@ func (ag *Agent) ClearViewImageAPI() {
func (ag *Agent) GetAction(api deepinfra.API, p model.Protocol, grp int64, role PermRole, isusersystem bool) ( func (ag *Agent) GetAction(api deepinfra.API, p model.Protocol, grp int64, role PermRole, isusersystem bool) (
reqs []zero.APIRequest, err error, reqs []zero.APIRequest, err error,
) { ) {
sysp, err := ag.system(role) sysp, err := ag.system(role, grp)
if err != nil { if err != nil {
return return
} }
@@ -243,6 +253,32 @@ func (ag *Agent) GetAction(api deepinfra.API, p model.Protocol, grp int64, role
return return
case !ag.manualaddreq: case !ag.manualaddreq:
ag.AddRequest(grp, &r) ag.AddRequest(grp, &r)
if !ag.manualaddmem && r.Action == SVM {
txt, ok := r.Params["text"].(string)
if !ok || txt == "" {
continue
}
txt, err := extractMemory(&r)
if err != nil {
ag.AddResponse(grp, &APIResponse{
Status: "error",
Message: err.Error(),
})
continue
}
err = ag.AddMemory(grp, txt)
s := "ok"
msg := ""
if err != nil {
s = "error"
msg = err.Error()
}
ag.AddResponse(grp, &APIResponse{
Status: s,
Message: msg,
})
continue
}
} }
reqs = append(reqs, r) reqs = append(reqs, r)
} }

44
memory.go Normal file
View File

@@ -0,0 +1,44 @@
package goba
import (
"errors"
"strings"
zero "github.com/wdvxdr1123/ZeroBot"
)
var (
errEmptyMempry = errors.New("empty memory")
errMemoryHasReturn = errors.New("memory has \\r|\\n")
)
type MemoryStorage interface {
Save(grp int64, text string) error
Load(grp int64) []string
}
func extractMemory(r *zero.APIRequest) (string, error) {
txt, ok := r.Params["text"].(string)
if !ok || txt == "" {
return "", errEmptyMempry
}
for _, c := range txt {
if c == '\r' || c == '\n' {
return "", errMemoryHasReturn
}
}
return txt, nil
}
func (ag *Agent) memoryof(grp int64) string {
mems := ag.mem.Load(grp)
if len(mems) == 0 {
return ""
}
sb := strings.Builder{}
for _, m := range mems {
sb.WriteByte('\n')
sb.WriteString(m)
}
return sb.String()
}

View File

@@ -5,6 +5,7 @@ import "testing"
const fulltab = `|功能|action|params|data| const fulltab = `|功能|action|params|data|
|---|---|---|---| |---|---|---|---|
|结束或暂停任务|end_action|-|-| |结束或暂停任务|end_action|-|-|
|持久化记忆|save_memory|text 简明扼要地用一句话概括你认为在该会话必须记住的一件事,禁止换行 (string)|-|
|发送私聊消息|send_private_msg|user_id 对方QQ号message 要发送的内容 (json.RawMessage)|message_id 消息ID (number)| |发送私聊消息|send_private_msg|user_id 对方QQ号message 要发送的内容 (json.RawMessage)|message_id 消息ID (number)|
|发送群消息|send_group_msg|group_id 群号message 要发送的内容 (json.RawMessage)|message_id 消息ID (number)| |发送群消息|send_group_msg|group_id 群号message 要发送的内容 (json.RawMessage)|message_id 消息ID (number)|
|撤回消息|delete_msg|message_id 消息ID|-| |撤回消息|delete_msg|message_id 消息ID|-|

View File

@@ -8,10 +8,13 @@ import (
//go:embed README.md //go:embed README.md
var sysp string var sysp string
func (ag *Agent) system(role PermRole) (string, error) { func (ag *Agent) system(role PermRole, grp int64) (string, error) {
tab, err := ag.perm.mdtable(role) tab, err := ag.perm.mdtable(role)
if err != nil { if err != nil {
return "", err return "", err
} }
return fmt.Sprintf(sysp, ag.id, ag.nickname, ag.sex, ag.chars, tab), nil return fmt.Sprintf(
sysp, ag.id, ag.nickname, ag.sex,
ag.chars, tab, ag.memoryof(grp),
), nil
} }

View File

@@ -64,3 +64,19 @@ type Terminus struct{}
func (Terminus) String() string { func (Terminus) String() string {
return `{"action":"` + EOA + `"}` return `{"action":"` + EOA + `"}`
} }
type Memory struct {
Action string `json:"action"`
GroupID int64 `json:"group_id,omitempty"` // QQ群号
UserID int64 `json:"user_id,omitempty"` // QQ号
Text string `json:"text"`
}
func (m *Memory) String() string {
sb := strings.Builder{}
err := json.NewEncoder(&sb).Encode(m)
if err != nil {
panic(errors.Wrap(err, "unexpected"))
}
return strings.TrimSpace(sb.String())
}