diff --git a/agent.go b/agent.go index 7f2dae1..0810eb5 100644 --- a/agent.go +++ b/agent.go @@ -2,60 +2,47 @@ package goba import ( "encoding/json" + "fmt" "io" "github.com/fumiama/deepinfra" + "github.com/fumiama/deepinfra/chat" "github.com/fumiama/deepinfra/model" "github.com/pkg/errors" zero "github.com/wdvxdr1123/ZeroBot" ) var ( - ErrZeroOrNegContextCap = errors.New("ctxcap <= 0") - ErrZeroOrNegEventCap = errors.New("evcap <= 0") - ErrPermissionDenied = errors.New("permission denied") + ErrPermissionDenied = errors.New("permission denied") ) // Agent is a OneBot event context, it is recommended to create one agent // per group or per user. type Agent struct { + log chat.Log[fmt.Stringer] id int64 nickname, sex string chars string - ctxcap, evcap int - // 64 bits or 32 bits gap - ctx generalctx - perm *Perm + perm *Perm } -// NewAgent characteristics 最好是 MD 格式 -func NewAgent(id int64, nickname, sex string, characteristics string) Agent { +// NewAgent characteristics 最好是 MD 格式, defaultprompt 是上下文为空时的默认项, 建议为 Event JSON +func NewAgent( + id int64, batchcap, itemscap int, + nickname, sex, characteristics, defaultprompt string, +) Agent { return Agent{ id: id, nickname: nickname, sex: sex, chars: characteristics, - ctxcap: 16, evcap: 8, + log: chat.NewLog[fmt.Stringer](batchcap, itemscap, "\n", defaultprompt), } } -func (ag *Agent) SetContextCap(n int) { - if n <= 0 { - panic(ErrZeroOrNegContextCap) - } - ag.ctxcap = n +func (ag *Agent) AddEvent(grp int64, ev *Event) { + ag.log.Add(grp, ev, false) } -func (ag *Agent) SetEventCap(n int) { - if n <= 0 { - panic(ErrZeroOrNegEventCap) - } - ag.evcap = n -} - -func (ag *Agent) AddEvent(ev *Event) { - addctx(&ag.ctx, ev, ag.ctxcap, ag.evcap) -} - -func (ag *Agent) AddRequest(req *zero.APIRequest) { - addctx(&ag.ctx, req, ag.ctxcap, ag.evcap) +func (ag *Agent) AddRequest(grp int64, req *zero.APIRequest) { + ag.log.Add(grp, req, true) } // GetAction get OneBot CallAction from LLM and add it to context. @@ -70,24 +57,15 @@ func (ag *Agent) AddRequest(req *zero.APIRequest) { // 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, m model.Protocol, role PermRole) ( +func (ag *Agent) GetAction(api deepinfra.API, p model.Protocol, grp int64, role PermRole, isusersystem bool) ( req zero.APIRequest, err error, ) { - p, err := ag.system(role) + sysp, err := ag.system(role) if err != nil { return } - m.System(p) - ag.ctx.mu.Lock() - for i, evs := range ag.ctx.ctx { - if i%2 == 0 { // is user input - m.User(evs.String()) - } else { // is agent callback - m.Assistant(evs.String()) - } - } - ag.ctx.mu.Unlock() + m := ag.log.Modelize(p, grp, sysp, isusersystem) resp, err := api.Request(m) if err != nil { @@ -101,7 +79,8 @@ func (ag *Agent) GetAction(api deepinfra.API, m model.Protocol, role PermRole) ( if !ag.perm.allow(role, req.Action) { err = errors.Wrap(ErrPermissionDenied, req.Action) } else { - ag.AddRequest(&req) + ag.AddRequest(grp, &req) } + return } diff --git a/ctx.go b/ctx.go deleted file mode 100644 index a98360f..0000000 --- a/ctx.go +++ /dev/null @@ -1,72 +0,0 @@ -package goba - -import ( - "reflect" - "sync" - - "github.com/pkg/errors" - zero "github.com/wdvxdr1123/ZeroBot" -) - -var ( - ErrInvalidFirstContextType = errors.New("invalid first context type") -) - -var ( - eventType = reflect.TypeOf(Event{}) - // requestType = reflect.TypeOf(zero.APIRequest{}) -) - -type generalctx struct { - mu sync.Mutex - ctx []events -} - -func addctx[T Event | zero.APIRequest]( - ctx *generalctx, v *T, ctxcap, evcap int, -) { - ctx.mu.Lock() - defer ctx.mu.Unlock() - - typ := reflect.TypeOf(v).Elem() - - if len(ctx.ctx) == 0 { - // must triggered by event - if !typ.AssignableTo(eventType) { - panic(errors.Wrap(ErrInvalidFirstContextType, typ.String())) - } - - // ctxcap & evcap must > 0, no need to check - evs := make(events, 1, evcap) - evs[0] = v - ctx.ctx = make([]events, 1, ctxcap) - ctx.ctx[0] = evs - return - } - // Get the last events slice - lastEvents := &ctx.ctx[len(ctx.ctx)-1] - - // Check if the type matches the first element of the last events - firstElemType := reflect.TypeOf((*lastEvents)[0]).Elem() - if typ.AssignableTo(firstElemType) { - // Same type, add to the last events - if len(*lastEvents) >= evcap { - // Shift elements forward by 1 (discard first element) - copy((*lastEvents)[:], (*lastEvents)[1:]) - (*lastEvents)[len(*lastEvents)-1] = any(v) - } else { - *lastEvents = append(*lastEvents, any(v)) - } - return - } - - // Different type or empty last events, create new events slice - if len(ctx.ctx) >= ctxcap { - // Shift elements forward by 2 (user-assistant pair) - copy(ctx.ctx[:], ctx.ctx[2:]) - ctx.ctx = ctx.ctx[:len(ctx.ctx)-2] - } - evs := make(events, 1, evcap) - evs[0] = v - ctx.ctx = append(ctx.ctx, evs) -} diff --git a/go.mod b/go.mod index 424ad81..d8b521c 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.20 require ( github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 - github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d + github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 github.com/pkg/errors v0.9.1 - github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250919145948-e8ffbbc995ac + github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250921063512-13752a73d444 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 0afc1e7..327dd5c 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9o github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d h1:iGxnST620IHrJ47DXkjzrZJ2rskBogWze+UyvnAxT6g= -github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= +github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1 h1:6PglFpNVm3DalGyRldacW2/v4jGWwn3v3q1tr2PhbVQ= +github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -22,8 +22,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250919145948-e8ffbbc995ac h1:XWpJQrUg75qUMwyKZX1jjK4ZziS5R4m/yVv5JCee3/s= -github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250919145948-e8ffbbc995ac/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M= +github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250921063512-13752a73d444 h1:7aYFXzvVr2zuxBvqrGaJb24Z4W12aXBdW8DuE1mteE4= +github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250921063512-13752a73d444/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/types.go b/types.go index fb0f966..4e5625d 100644 --- a/types.go +++ b/types.go @@ -8,19 +8,6 @@ import ( zero "github.com/wdvxdr1123/ZeroBot" ) -type events []any - -func (evs events) String() string { - sb := strings.Builder{} - for _, ev := range evs { - err := json.NewEncoder(&sb).Encode(ev) // has been terminated with '\n' - if err != nil { - panic(errors.Wrap(err, "unexpected")) - } - } - return strings.TrimSpace(sb.String()) -} - // Event is the simplified OneBot event that dumped to the agent in JSON format type Event struct { Time int64 `json:"time"` // 事件发生的时间戳 @@ -41,3 +28,12 @@ type Event struct { Sender *zero.User `json:"sender,omitempty"` // 事件发送者个人信息 Message json.RawMessage `json:"message,omitempty"` // JSON 格式的消息内容 } + +func (ev *Event) String() string { + sb := strings.Builder{} + err := json.NewEncoder(&sb).Encode(ev) + if err != nil { + panic(errors.Wrap(err, "unexpected")) + } + return strings.TrimSpace(sb.String()) +}