ci / vet-and-test (pull_request) Successful in 1m24s
Why: the parent sync needs a backend commit that contains both vision routing and the current main pin. Evidence: incoming pin80bb123is an ancestor of origin/main; merge keepscfde38d. Impact: backend gitlink can record one resolved commit. Refs: feature/four-model-vision
143 lines
5.0 KiB
Go
143 lines
5.0 KiB
Go
// Package profiles 管理智能体画像(4.7):数据化、可导入导出;内置默认画像(T0 基线)。
|
||
package profiles
|
||
|
||
import (
|
||
"reflect"
|
||
|
||
"agent-system-backend/internal/store"
|
||
core "agent-system-core"
|
||
)
|
||
|
||
// OwnerDefault 与 store 保持一致的默认租户(内置画像共享,owner=”)。
|
||
const OwnerDefault = store.OwnerDefault
|
||
|
||
const DefaultProfileID = "default"
|
||
const FastProfileID = "fast"
|
||
const ReasoningProfileID = "reasoning"
|
||
const WritingProfileID = "writing"
|
||
|
||
// defaultSystemPrompt 默认画像不再携带常驻提示词:基座(身份/行为/谨慎/工具/风格)
|
||
// 与环境段由提示词库组装(prompts 库 base.*/env.*);文档任务输出要求已技能化
|
||
// (task.doc-minutes 等,按触发注入)。SystemPrompt 留空 = 完全走库。
|
||
const defaultSystemPrompt = ""
|
||
|
||
func DefaultProfile() core.AgentProfile {
|
||
return core.AgentProfile{
|
||
ID: DefaultProfileID,
|
||
Name: "通用助手",
|
||
Description: "内置默认画像:全部工具可用,HTTP 工具执行前确认(T0 基线)。对话模型由系统设置决定。",
|
||
Builtin: true,
|
||
Source: "builtin",
|
||
SystemPrompt: defaultSystemPrompt,
|
||
Model: core.ModelRoute{},
|
||
Tools: core.ToolPolicy{Allow: []string{"*"}, Confirm: []string{"http."}},
|
||
Context: core.ContextPolicy{MaxHistoryMessages: 40, MaxToolResultChars: 20000},
|
||
HITL: core.HITLPolicy{AskOnAmbiguity: true},
|
||
// 空预算 = 不限制轮次、工具调用数和输出 token。用户自建画像可以自行设上限。
|
||
Budget: core.RunBudget{},
|
||
}
|
||
}
|
||
|
||
// BuiltinProfiles 是启动时幂等写入的全部内置画像。模型名是网关模型 ID,
|
||
// 端点与密钥沿用系统设置。轻量档留空则回落设置里的轻量模型。
|
||
func BuiltinProfiles() []core.AgentProfile {
|
||
fast := DefaultProfile()
|
||
fast.ID = FastProfileID
|
||
fast.Name = "快速助手"
|
||
fast.Description = "内置画像:对话和轻量任务都用 qwen3.8-flash,适合短问答和改写。"
|
||
fast.Model = core.ModelRoute{Chat: "qwen3.8-flash", Lightweight: "qwen3.8-flash"}
|
||
|
||
reasoning := DefaultProfile()
|
||
reasoning.ID = ReasoningProfileID
|
||
reasoning.Name = "深度推理"
|
||
reasoning.Description = "内置画像:对话用 ZHIPU/GLM-5.3-FlashX。分诊和校验仍走系统轻量模型。"
|
||
reasoning.Model = core.ModelRoute{Chat: "ZHIPU/GLM-5.3-FlashX"}
|
||
|
||
writing := DefaultProfile()
|
||
writing.ID = WritingProfileID
|
||
writing.Name = "写作与文档"
|
||
writing.Description = "内置画像:对话用 ZHIPU/GLM-5.3-Flash。分诊和校验仍走系统轻量模型。"
|
||
writing.Model = core.ModelRoute{Chat: "ZHIPU/GLM-5.3-Flash"}
|
||
|
||
return []core.AgentProfile{DefaultProfile(), fast, reasoning, writing}
|
||
}
|
||
|
||
func builtinID(id string) bool {
|
||
for _, p := range BuiltinProfiles() {
|
||
if p.ID == id {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
type Service struct{ st *store.Store }
|
||
|
||
func New(st *store.Store) *Service { return &Service{st: st} }
|
||
|
||
// EnsureDefault 幂等写入全部内置画像;内置画像只读(Save 拒绝编辑),库中与内置定义
|
||
// 有差异的行只可能来自更早版本的内置定义,因此差异时升级为当前内置定义。
|
||
func (s *Service) EnsureDefault() error {
|
||
for _, p := range BuiltinProfiles() {
|
||
if cur, err := s.st.GetProfile(OwnerDefault, p.ID); err == nil {
|
||
if reflect.DeepEqual(cur, p) {
|
||
continue
|
||
}
|
||
}
|
||
if err := s.st.SaveProfile(p); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (s *Service) List(owner string) ([]core.AgentProfile, error) { return s.st.ListProfiles(owner) }
|
||
|
||
func (s *Service) Get(owner, id string) (core.AgentProfile, error) {
|
||
p, err := s.st.GetProfile(owner, id)
|
||
if err == nil {
|
||
return p, nil
|
||
}
|
||
// 空或未知画像回落默认(T0 行为基线)。
|
||
return DefaultProfile(), nil
|
||
}
|
||
|
||
func (s *Service) Save(owner string, p core.AgentProfile) error {
|
||
if builtinID(p.ID) {
|
||
return errReadOnly("profiles: builtin profile is read-only; duplicate and modify")
|
||
}
|
||
if p.Tools.Allow == nil {
|
||
p.Tools.Allow = []string{"*"}
|
||
}
|
||
p.Owner = owner
|
||
return s.st.SaveProfile(p)
|
||
}
|
||
|
||
func (s *Service) Delete(owner, id string) error {
|
||
if builtinID(id) {
|
||
return errReadOnly("profiles: builtin profile is read-only")
|
||
}
|
||
return s.st.DeleteProfile(owner, id)
|
||
}
|
||
|
||
// SaveAccountChecked is the user-facing write path. Builtin profiles remain
|
||
// read-only and the Store binds the write to the actor's effective account.
|
||
func (s *Service) SaveAccountChecked(actor string, p core.AgentProfile, allowImplicitDefault bool) error {
|
||
if p.ID == DefaultProfileID || p.Builtin {
|
||
return errReadOnly("profiles: builtin profile is read-only; duplicate and modify")
|
||
}
|
||
if p.Tools.Allow == nil {
|
||
p.Tools.Allow = []string{"*"}
|
||
}
|
||
p.Owner = actor
|
||
return s.st.SaveProfileAccountChecked(actor, p, allowImplicitDefault)
|
||
}
|
||
|
||
func (s *Service) DeleteAccountChecked(actor, id string, allowImplicitDefault bool) error {
|
||
return s.st.DeleteProfileAccountChecked(actor, id, allowImplicitDefault)
|
||
}
|
||
|
||
type errReadOnly string
|
||
|
||
func (e errReadOnly) Error() string { return string(e) }
|