feat(model): 工作模型与 JARVIS 语音模型分开配置 + 语音任务路由到快模型
模型配置加"用途"维度:工作主力(chat,要强)与 JARVIS 语音(voice,要快/低时延)各配各激活, 语音任务走语音模型池,未配置则透明回落工作模型——不影响现有功能。 - contract: ConfigKindVoice="voice" + Meta[model_profile]=voice(与 intent==report 同类路由) - gateway: ServeConfig/broadcastActive 循环纳入 voice;submitVoiceTask 打 model_profile=voice 标记 - dispatcher: 第二个 llm.Pool(voicePool)吃 voice 配置热更新;board.useVoice 从 Meta 派生(含快照); Orchestrator.agentPool(b) 按黑板选池——语音且语音池就绪→语音池,否则工作池; agent 生成路径(graph/react/coordinator/compose)全改走 agentPool(b),报告/护栏/记忆固定工作池 - admin: 模型页三 Tab(工作主力/JARVIS语音/向量化),复用 ModelManager;api Kind 加 voice Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ type boardSnapshot struct {
|
||||
UID string `json:"uid,omitempty"`
|
||||
SID string `json:"sid,omitempty"`
|
||||
Query string `json:"query,omitempty"`
|
||||
UseVoice bool `json:"use_voice,omitempty"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
History []*schema.Message `json:"history,omitempty"`
|
||||
KB string `json:"kb,omitempty"`
|
||||
@@ -35,6 +36,7 @@ func (b *board) MarshalJSON() ([]byte, error) {
|
||||
UID: b.uid,
|
||||
SID: b.sid,
|
||||
Query: b.query,
|
||||
UseVoice: b.useVoice,
|
||||
Profile: b.profile,
|
||||
History: b.history,
|
||||
KB: b.kb,
|
||||
@@ -56,6 +58,7 @@ func (b *board) UnmarshalJSON(data []byte) error {
|
||||
b.uid = s.UID
|
||||
b.sid = s.SID
|
||||
b.query = s.Query
|
||||
b.useVoice = s.UseVoice
|
||||
b.profile = s.Profile
|
||||
b.history = s.History
|
||||
b.kb = s.KB
|
||||
|
||||
@@ -56,9 +56,10 @@ func (o *Orchestrator) execComposeGraph(ctx context.Context, t *contract.Task, t
|
||||
flow, ferr := dsl.Parse(t.Graph)
|
||||
plan := dsl.Compile(t.Graph)
|
||||
b := &board{
|
||||
uid: meta(t, contract.MetaUserID),
|
||||
sid: meta(t, contract.MetaSessionID),
|
||||
query: plan.Query,
|
||||
uid: meta(t, contract.MetaUserID),
|
||||
sid: meta(t, contract.MetaSessionID),
|
||||
query: plan.Query,
|
||||
useVoice: meta(t, contract.MetaModelProfile) == contract.ModelProfileVoice, // 语音任务 → 走语音模型池
|
||||
}
|
||||
|
||||
// 无图/空图:退化为 compose 单轮对话。
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// START → ChatModel 节点 → END,编译为 Runnable 后流式执行;可观测经 callbacks 桥到 ExecEvent。
|
||||
// 模型未就绪 / 编译失败时降级回 runAgent(同样的流式回流,保证不回归)。
|
||||
func (o *Orchestrator) runComposeConversation(ctx context.Context, taskID string, b *board, system string, tr *execTracer, node, label string) {
|
||||
cm := o.pool.ChatModel()
|
||||
cm := o.agentPool(b).ChatModel() // 语音任务走语音模型池
|
||||
if cm == nil {
|
||||
o.runAgent(ctx, taskID, b, system, tr, node, label) // 无模型 → 降级桩
|
||||
return
|
||||
|
||||
@@ -154,7 +154,7 @@ func (o *Orchestrator) buildSpecialists(ctx context.Context, specs []specialistS
|
||||
usedFuncNames := map[string]bool{}
|
||||
for i, spec := range specs {
|
||||
sys := firstNonEmpty(spec.System, defaultAgentSystem) + specialistCondensedSuffix
|
||||
run := o.specialistRunner(ctx, spec, sys, byName)
|
||||
run := o.specialistRunner(ctx, b, spec, sys, byName)
|
||||
if run == nil {
|
||||
tr.info("coordinator", "system", "专家跳过", "无可用模型:"+spec.Name)
|
||||
continue
|
||||
@@ -175,7 +175,7 @@ func (o *Orchestrator) buildSpecialists(ctx context.Context, specs []specialistS
|
||||
|
||||
// specialistRunner 构造专家执行闭包:带工具且模型支持函数调用→react.Agent.Generate;否则→ChatModel.Generate。
|
||||
// 无可用模型返回 nil(该专家被跳过)。
|
||||
func (o *Orchestrator) specialistRunner(ctx context.Context, spec specialistSpec, sys string, byName map[string]tool.BaseTool) func(context.Context, string) (string, error) {
|
||||
func (o *Orchestrator) specialistRunner(ctx context.Context, b *board, spec specialistSpec, sys string, byName map[string]tool.BaseTool) func(context.Context, string) (string, error) {
|
||||
var tools []tool.BaseTool
|
||||
for _, tn := range spec.Tools {
|
||||
if t, ok := byName[tn]; ok {
|
||||
@@ -183,7 +183,7 @@ func (o *Orchestrator) specialistRunner(ctx context.Context, spec specialistSpec
|
||||
}
|
||||
}
|
||||
if len(tools) > 0 {
|
||||
if tcm := o.pool.ToolCallingModel(); tcm != nil {
|
||||
if tcm := o.agentPool(b).ToolCallingModel(); tcm != nil {
|
||||
ag, err := react.NewAgent(ctx, &react.AgentConfig{
|
||||
ToolCallingModel: tcm,
|
||||
ToolsConfig: compose.ToolsNodeConfig{Tools: tools},
|
||||
@@ -202,7 +202,7 @@ func (o *Orchestrator) specialistRunner(ctx context.Context, spec specialistSpec
|
||||
}
|
||||
// 工具型专家但模型不支持函数调用 → 退纯对话(下方)
|
||||
}
|
||||
cm := o.pool.ChatModel()
|
||||
cm := o.agentPool(b).ChatModel()
|
||||
if cm == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -219,7 +219,7 @@ func (o *Orchestrator) specialistRunner(ctx context.Context, spec specialistSpec
|
||||
// Anthropic 配方(分解→定制简报→并行派发→综合)。无 ToolCallingModel / 0 可用专家 → 降级单 agent。
|
||||
func (o *Orchestrator) runCoordinator(ctx context.Context, taskID string, b *board, system string, n dsl.Node, tr *execTracer, node string) {
|
||||
specs := parseSpecialists(n.Config)
|
||||
tcm := o.pool.ToolCallingModel()
|
||||
tcm := o.agentPool(b).ToolCallingModel()
|
||||
if tcm == nil || len(specs) == 0 {
|
||||
tr.info(node, "system", "协调者降级", "模型不支持函数调用或未配置专家,退回普通对话")
|
||||
o.runAgent(ctx, taskID, b, system, tr, node, labelOf(n, "多智能体协调"))
|
||||
|
||||
@@ -21,6 +21,7 @@ const defaultAgentSystem = "你是 sundynix-agentix 平台的 AI 助手。"
|
||||
type board struct {
|
||||
uid, sid string
|
||||
query string
|
||||
useVoice bool // 该任务用 JARVIS 语音模型池(网关 Meta[model_profile]==voice)
|
||||
profile string
|
||||
history []*schema.Message
|
||||
kb string // 最近一个检索节点的 owner 作用域库名(供 map 并行各项检索)
|
||||
@@ -178,10 +179,11 @@ func (o *Orchestrator) runAgent(ctx context.Context, taskID string, b *board, sy
|
||||
var reasoning strings.Builder
|
||||
onReasoning := func(s string) { reasoning.WriteString(s) }
|
||||
var err error
|
||||
if o.pool.Ready() {
|
||||
err = o.pool.ChatStream(ctx, toChatMessages(msgs), send, onReasoning)
|
||||
pool := o.agentPool(b) // 语音任务走语音模型池(否则工作池)
|
||||
if pool.Ready() {
|
||||
err = pool.ChatStream(ctx, toChatMessages(msgs), send, onReasoning)
|
||||
} else {
|
||||
err = o.pool.StreamText(ctx, replyFor(msgs), func(tok []byte) { send(string(tok)) })
|
||||
err = pool.StreamText(ctx, replyFor(msgs), func(tok []byte) { send(string(tok)) })
|
||||
}
|
||||
if rc := reasoning.String(); rc != "" {
|
||||
tr.info(node, "model", "推理过程", fmt.Sprintf("思考 %d 字:%s", len([]rune(rc)), truncate(rc, 200)))
|
||||
|
||||
@@ -99,6 +99,7 @@ const specialistTimeout = 3 * time.Minute
|
||||
// Orchestrator 把每个 DSL 任务动态编译为 Eino 图并执行(记忆召回 → 工具节点 → 注入 → 流式)。
|
||||
type Orchestrator struct {
|
||||
pool LLM
|
||||
voicePool LLM // JARVIS 语音模型池(可为 nil → 语音任务回落 pool)
|
||||
breaker *harness.CircuitBreaker
|
||||
eval *harness.Evaluator
|
||||
sink TokenSink
|
||||
@@ -122,6 +123,19 @@ func NewOrchestrator(pool LLM, breaker *harness.CircuitBreaker, eval *harness.Ev
|
||||
return &Orchestrator{pool: pool, breaker: breaker, eval: eval, sink: sink, tools: tools, exec: exec, status: status, approval: approval, evalSink: evalSink}, nil
|
||||
}
|
||||
|
||||
// SetVoicePool 注入 JARVIS 语音模型池(可选)。注入且就绪时,标了 model_profile==voice 的任务
|
||||
// 用它跑,抢首字时延;不注入或未就绪则语音任务透明回落工作模型池(o.pool)。
|
||||
func (o *Orchestrator) SetVoicePool(p LLM) { o.voicePool = p }
|
||||
|
||||
// agentPool 按黑板选本任务该用的模型池:语音任务且语音池就绪 → 语音池;否则工作池。
|
||||
// 只用于面向用户的 agent 生成(对话/协作);报告/护栏/记忆抽取等固定走工作池。
|
||||
func (o *Orchestrator) agentPool(b *board) LLM {
|
||||
if b != nil && b.useVoice && o.voicePool != nil && o.voicePool.Ready() {
|
||||
return o.voicePool
|
||||
}
|
||||
return o.pool
|
||||
}
|
||||
|
||||
// SetGuardian 注入输入护栏 Tier2 的 LLM 分类器(可选;不注入则灰区任务直接放行执行)。
|
||||
func (o *Orchestrator) SetGuardian(c *harness.Classifier) { o.guard = c }
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ func (o *Orchestrator) discoverTools(subject func(string) string, b *board, task
|
||||
// runReactAgent 执行带"自主工具"的 agent 节点:模型在 ReAct 循环里自行决定调哪些 MCP 工具。
|
||||
// 模型不支持函数调用 / 无工具时降级回普通 runAgent。最终答复流式回流;工具调用由适配器落轨迹。
|
||||
func (o *Orchestrator) runReactAgent(ctx context.Context, taskID string, b *board, system string, n dsl.Node, tr *execTracer, node string) {
|
||||
tcm := o.pool.ToolCallingModel()
|
||||
tcm := o.agentPool(b).ToolCallingModel()
|
||||
tools := o.agentTools(b, taskID, tr)
|
||||
if tcm == nil || len(tools) == 0 {
|
||||
tr.info(node, "system", "ReAct 降级", "模型不支持函数调用或无可用工具,退回普通对话")
|
||||
|
||||
@@ -146,6 +146,16 @@ func (s *Subscriber) FetchModelConfigWithRetry(ctx context.Context, apply func(*
|
||||
s.inner.RequestConfigWithRetry(ctx, contract.ConfigKindChat, apply)
|
||||
}
|
||||
|
||||
// SubscribeVoiceConfigUpdated 订阅 JARVIS 语音模型配置热更新(可空——未配置语音模型时不生效)。
|
||||
func (s *Subscriber) SubscribeVoiceConfigUpdated(onUpdate func(*contract.ModelConfig)) (func() error, error) {
|
||||
return s.inner.SubscribeConfigUpdated(contract.ConfigKindVoice, onUpdate)
|
||||
}
|
||||
|
||||
// FetchVoiceConfigWithRetry 后台重试拉取初始语音模型配置(未配置则一直拿不到,语音任务回落工作模型)。
|
||||
func (s *Subscriber) FetchVoiceConfigWithRetry(ctx context.Context, apply func(*contract.ModelConfig)) {
|
||||
s.inner.RequestConfigWithRetry(ctx, contract.ConfigKindVoice, apply)
|
||||
}
|
||||
|
||||
// FetchPromptsWithRetry 后台重试拉取初始激活 prompt 集(覆盖内置默认)。
|
||||
func (s *Subscriber) FetchPromptsWithRetry(ctx context.Context, apply func(map[string]string)) {
|
||||
s.inner.RequestPromptsWithRetry(ctx, apply)
|
||||
|
||||
Reference in New Issue
Block a user