1
0
mirror of https://github.com/fumiama/go-onebot-agent.git synced 2026-07-02 09:50:36 +08:00

feat: GetAction return multiple reqs

This commit is contained in:
源文雨
2025-09-22 22:23:17 +08:00
parent d45d7d3ec5
commit a21b15d4c1
3 changed files with 83 additions and 75 deletions

View File

@@ -13,55 +13,50 @@
## OneBot 11 协议 ## OneBot 11 协议
> 输入输出均为 JSON 格式的一系列 Object代表一系列事件或响应以换行符分隔。 > 输入输出均为 JSON 格式的一系列 Object代表一系列事件或响应以换行符分隔。
> 所有可能的字段由对应的 Golang 结构体给出。 > 所有可能的字段如下type 由对应的 Golang 类型给出。
### 输入 ### 输入
事件 (Event) 是输入的基本单位, 事件 (Event) 是输入的基本单位,
```go |key|type|说明|
type Event struct { |---|---|---|
Time int64 `json:"time"` // 事件发生的时间戳 |time|int64|事件发生的时间戳|
PostType string `json:"post_type"` // 上报类型: message / notice / request |post_type|string|上报类型: message / notice / request|
MessageType string `json:"message_type"` // message 类型: group / private |message_type|string|message 类型: group / private|
SubType string `json:"sub_type"` // message 子类型: normal (一般消息) / notice (灰色小字通知) |sub_type|string|message 子类型: normal (一般消息) / notice (灰色小字通知)|
MessageID int64 `json:"message_id"` // 消息 ID, 唯一标识该事件 |message_id|int64|消息 ID, 唯一标识该事件|
GroupID int64 `json:"group_id"` // QQ群号 |group_id|int64|QQ群号|
UserID int64 `json:"user_id"` // 事件发送者QQ号 |user_id|int64|事件发送者QQ号|
TargetID int64 `json:"target_id"` |target_id|int64|后述|
SelfID int64 `json:"self_id"` // 收到事件的QQ号 (你的ID) |self_id|int64|收到事件的QQ号 (你的ID)|
NoticeType string `json:"notice_type,omitempty"` |notice_type|string|后述|
OperatorID int64 `json:"operator_id"` // This field is used for Notice Event |operator_id|int64|For Notice Event|
File *File `json:"file,omitempty"` |file|*File|后述|
RequestType string `json:"request_type,omitempty"` |request_type|string|后述|
Flag string `json:"flag,omitempty"` |flag|string|后述|
Comment string `json:"comment,omitempty"` // This field is used for Request Event |comment|string|For Request Event|
Sender *User `json:"sender,omitempty"` // 事件发送者个人信息 |sender|*User|事件发送者个人信息|
Message json.RawMessage `json:"message,omitempty"` // JSON 格式的消息内容 |message|json.RawMessage|JSON 格式的消息内容|
}
```
其中,文件 (File) 标识一个聊天文件, 其中,文件 (File) 标识一个聊天文件,
```go |key|type|
type File struct { |---|---|
ID string `json:"id"` |id|string|
Name string `json:"name"` |name|string|
Size int64 `json:"size"` |size|int64|
BusID int64 `json:"busid"`
}
```
用户 (User) 标识一个QQ用户 用户 (User) 标识一个QQ用户
```go |key|type|说明|
type User struct { |---|---|---|
ID int64 `json:"user_id"` |user_id|int64|用户QQ号|
NickName string `json:"nickname"` |nickname|string|昵称|
Sex string `json:"sex"` // "male"、"female"、"unknown" |sex|string|"male"、"female"、"unknown"|
Age int `json:"age"` |age|int|年龄|
Area string `json:"area"` // 地区 |area|string|地区|
// 以下为群聊特有字段 |card|string|群名片/备注(群聊特有|
Card string `json:"card"` // 群名片/备注 |title|string|专属头衔(群聊特有)|
Title string `json:"title"` // 专属头衔 |level|string|群聊等级(群聊特有)|
Level string `json:"level"` // 群聊等级 |role|string|"owner"、"admin"、"member"(群聊特有)|
Role string `json:"role"` // "owner"、"admin"、"member"
}
```
#### 详细事件种类 #### 详细事件种类
|类型|post_type|message_type|sub_type|message_id|group_id|user_id|target_id|self_id|notice_type|operator_id|file|request_type|flag|comment|sender|message| |类型|post_type|message_type|sub_type|message_id|group_id|user_id|target_id|self_id|notice_type|operator_id|file|request_type|flag|comment|sender|message|

View File

@@ -4,7 +4,7 @@ package goba
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "strings"
"github.com/fumiama/deepinfra" "github.com/fumiama/deepinfra"
"github.com/fumiama/deepinfra/chat" "github.com/fumiama/deepinfra/chat"
@@ -26,16 +26,23 @@ type Agent struct {
nickname, sex string nickname, sex string
chars string chars string
perm *Perm perm *Perm
manualaddreq bool
} }
// NewAgent characteristics 最好是 MD 格式, defaultprompt 是上下文为空时的默认项, 建议为 Event JSON // NewAgent 创建一个 Agent 实例。
//
// - characteristics 推荐使用 Markdown 格式,描述 Agent 个性。
// - defaultprompt 为上下文为空时的默认提示,建议为事件的 JSON一般不会用到因此也可留空。
// - manualaddreq 表示是否由用户手动添加请求。
func NewAgent( func NewAgent(
id int64, batchcap, itemscap int, id int64, batchcap, itemscap int,
nickname, sex, characteristics, defaultprompt string, nickname, sex, characteristics, defaultprompt string,
manualaddreq 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,
log: chat.NewLog[fmt.Stringer](batchcap, itemscap, "\n", defaultprompt), log: chat.NewLog[fmt.Stringer](batchcap, itemscap, "\n", defaultprompt),
manualaddreq: manualaddreq,
} }
_ = ag.LoadPermTable() _ = ag.LoadPermTable()
return return
@@ -55,16 +62,12 @@ func (ag *Agent) AddRequest(grp int64, req *zero.APIRequest) {
// //
// Note: // Note:
// //
// 1. The response may be empty, meaning that LLM do not want // - If LLM returns an invalid action, ErrPermissionDenied will be returned
// to react with these events. In this case, this function will // with complete reqs before invalid call, caller may decide whether to use
// return io.EOF and the context will be left no change. // these reqs by themselves. Whatever, invalid req will not be added into
// // the context. You may call AddRequest to add it but it is not recommended.
// 2. If LLM returns an invalid action, ErrPermissionDenied will be returned
// with complete req, caller may decide whether to use this req by themselves.
// Whatever, this req will not be added into the context. You may call
// AddRequest to add it but it is not recommended.
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) (
req zero.APIRequest, err error, reqs []zero.APIRequest, err error,
) { ) {
sysp, err := ag.system(role) sysp, err := ag.system(role)
if err != nil { if err != nil {
@@ -77,15 +80,25 @@ func (ag *Agent) GetAction(api deepinfra.API, p model.Protocol, grp int64, role
if err != nil { if err != nil {
return return
} }
err = json.Unmarshal([]byte(resp), &req) reqs = make([]zero.APIRequest, 0, 2)
if err == nil && req.Action == "" { dec := json.NewDecoder(strings.NewReader(resp))
err = io.EOF for dec.More() {
r := zero.APIRequest{}
err = dec.Decode(&r)
if err != nil {
break
} }
if r.Action == "" {
if !ag.perm.allow(role, req.Action) { continue
err = errors.Wrap(ErrPermissionDenied, req.Action) }
} else { switch {
ag.AddRequest(grp, &req) case !ag.perm.allow(role, r.Action):
err = errors.Wrap(ErrPermissionDenied, r.Action)
return
case !ag.manualaddreq:
ag.AddRequest(grp, &r)
}
reqs = append(reqs, r)
} }
return return

View File

@@ -13,14 +13,14 @@ type Event struct {
Time int64 `json:"time"` // 事件发生的时间戳 Time int64 `json:"time"` // 事件发生的时间戳
PostType string `json:"post_type"` // 上报类型: message / notice / request PostType string `json:"post_type"` // 上报类型: message / notice / request
MessageType string `json:"message_type"` // message 类型: group / private MessageType string `json:"message_type"` // message 类型: group / private
SubType string `json:"sub_type"` // message 子类型: normal (一般消息) / notice (灰色小字通知) SubType string `json:"sub_type,omitempty"` // message 子类型: normal (一般消息) / notice (灰色小字通知)
MessageID int64 `json:"message_id"` // 消息 ID, 唯一标识该事件 MessageID int64 `json:"message_id"` // 消息 ID, 唯一标识该事件
GroupID int64 `json:"group_id"` // QQ群号 GroupID int64 `json:"group_id,omitempty"` // QQ群号
UserID int64 `json:"user_id"` // 事件发送者QQ号 UserID int64 `json:"user_id"` // 事件发送者QQ号
TargetID int64 `json:"target_id"` TargetID int64 `json:"target_id,omitempty"`
SelfID int64 `json:"self_id"` // 收到事件的QQ号 (你的ID) SelfID int64 `json:"self_id"` // 收到事件的QQ号 (你的ID)
NoticeType string `json:"notice_type,omitempty"` NoticeType string `json:"notice_type,omitempty"`
OperatorID int64 `json:"operator_id"` // This field is used for Notice Event OperatorID int64 `json:"operator_id,omitempty"` // This field is used for Notice Event
File *zero.File `json:"file,omitempty"` File *zero.File `json:"file,omitempty"`
RequestType string `json:"request_type,omitempty"` RequestType string `json:"request_type,omitempty"`
Flag string `json:"flag,omitempty"` Flag string `json:"flag,omitempty"`