From 1c4b158a3fd24ab11768150e4e157cba74e08d30 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 28 Aug 2026 12:26:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E4=BC=9A=E8=AF=9D=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E9=80=89=E6=8B=A9=E6=8C=81=E4=B9=85=E5=8C=96=E2=80=94?= =?UTF-8?q?=E2=80=94agent=5Fchat=20=E4=BC=A0=20model=5Fid=E3=80=81?= =?UTF-8?q?=E4=B8=8B=E6=8B=89=E6=8C=89=E9=A1=B9=E7=9B=AE=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E9=80=89=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - agent_chat.dspy: 提取前端 model_id 传给 gateway.run_message(校验+持久化到项目) - agent_model_options.dspy: selected 标记=当前项目已设模型(按 session_id 解析, 无项目回退个人全局)——下拉每次打开都恢复项目当前模型,不回退首项 - agent/index.ui: 模型下拉带 session_id(多 tab 各自项目,下拉解析对的项目) --- wwwroot/agent/index.ui | 4 ++-- wwwroot/api/agent_chat.dspy | 5 ++++- wwwroot/api/agent_model_options.dspy | 24 +++++++++++++++++++++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/wwwroot/agent/index.ui b/wwwroot/agent/index.ui index d84c506..77b3860 100644 --- a/wwwroot/agent/index.ui +++ b/wwwroot/agent/index.ui @@ -18,7 +18,7 @@ "event": "click", "actiontype": "script", "target": "self", - "script": "var tp=bricks.getWidgetById('session_tabs',bricks.app);if(!tp)return;var sid='s'+Date.now()+'_'+Math.floor(Math.random()*100000);var n=tp.opts.items.length+1;tp.open_tab({name:'session_'+sid,label:'会话 '+n,removable:true,content:{widgettype:'VBox',options:{css:'filler',width:'100%',height:'100%'},subwidgets:[{widgettype:'urlwidget',options:{url:'/pipeline_core/api/agent_menus.dspy?session_id='+sid,method:'GET'}},{widgettype:'AgentIO',options:{css:'filler',margin:'0 24px 24px 24px',url:'/pipeline_core/api/agent_chat.dspy?session_id='+sid,model_dataurl:'/pipeline_core/api/agent_model_options.dspy',model_cwidth:14,placeholder:'输入你的需求...'}}]}});" + "script": "var tp=bricks.getWidgetById('session_tabs',bricks.app);if(!tp)return;var sid='s'+Date.now()+'_'+Math.floor(Math.random()*100000);var n=tp.opts.items.length+1;tp.open_tab({name:'session_'+sid,label:'会话 '+n,removable:true,content:{widgettype:'VBox',options:{css:'filler',width:'100%',height:'100%'},subwidgets:[{widgettype:'urlwidget',options:{url:'/pipeline_core/api/agent_menus.dspy?session_id='+sid,method:'GET'}},{widgettype:'AgentIO',options:{css:'filler',margin:'0 24px 24px 24px',url:'/pipeline_core/api/agent_chat.dspy?session_id='+sid,model_dataurl:'/pipeline_core/api/agent_model_options.dspy?session_id='+sid,model_cwidth:14,placeholder:'输入你的需求...'}}]}});" }] } ] @@ -50,7 +50,7 @@ "css": "filler", "margin": "0 24px 24px 24px", "url": "/pipeline_core/api/agent_chat.dspy?session_id=default", - "model_dataurl": "/pipeline_core/api/agent_model_options.dspy", + "model_dataurl": "/pipeline_core/api/agent_model_options.dspy?session_id=default", "model_cwidth": 14, "placeholder": "输入你的需求..." } diff --git a/wwwroot/api/agent_chat.dspy b/wwwroot/api/agent_chat.dspy index 82d7686..03f4d3d 100644 --- a/wwwroot/api/agent_chat.dspy +++ b/wwwroot/api/agent_chat.dspy @@ -80,12 +80,15 @@ if action == 'send_message': pipeline_id = (params_kw or {}).get('pipeline_id', '') or '' # ── 走 gateway 统一入口(Web AgentIO 通道)── + # model_id:前端模型下拉选中的模型 → gateway 校验后持久化到项目(项目模型一经设置 + # 即生效,直到用户再次选择),空 = 沿用项目已设模型。 + model_id = (params_kw or {}).get('model_id', '') or '' from pipeline_service.gateway import get_gateway gateway = get_gateway() async def agent_stream(): _base = entire_url("/") - async for chunk in gateway.run_message("web", uid, prompt, base_url=_base, session_id=session_id, pipeline_id=pipeline_id): + async for chunk in gateway.run_message("web", uid, prompt, base_url=_base, session_id=session_id, pipeline_id=pipeline_id, model_id=model_id): data = json.loads(chunk) t = data.get('type', '') diff --git a/wwwroot/api/agent_model_options.dspy b/wwwroot/api/agent_model_options.dspy index f94bdbf..f0e1aab 100644 --- a/wwwroot/api/agent_model_options.dspy +++ b/wwwroot/api/agent_model_options.dspy @@ -1,11 +1,14 @@ # agent_model_options.dspy - 返回 active 模型列表(供 AgentIO 模型选择下拉) # llm 表是 pipeline 库的通用配置,产线无关 # org_id 多租户隔离:非系统级机构只看到本机构的模型(不含系统级兜底) -# 返回每项带 selected 标记,标识个人之前选择的默认模型(default_llm_id) +# selected 标记 = 当前项目的已设模型(sd_projects.default_model,产线通用、跨会话持久); +# 项目未设模型时回退个人全局选择(default_llm_id)。前端 UiCode 按 selected 恢复选中项, +# 不再每次重建都回退第一项。 dbname = get_module_dbname('pipeline_core') uid = await get_user() org_id = (await get_userorgid()) or '' +session_id = (params_kw or {}).get('session_id', '') or '' async with DBPools().sqlorContext(dbname) as sor: sql = "SELECT id, name, provider, model_id, capabilities FROM llm WHERE status='active'" @@ -16,7 +19,22 @@ async with DBPools().sqlorContext(dbname) as sor: sql += " ORDER BY name" recs = await sor.sqlExe(sql, params) - # 个人之前选择的默认模型 + # 项目级模型(优先级最高):按会话解析当前项目 → sd_projects.default_model。 + # session_id 为空/无会话记录时自动回退全局设置,与消息链路的项目解析同一函数,不漂移。 + project_model = '' + try: + from pipeline_service.workspace import get_session_project_id + _pid = await get_session_project_id(sor, uid or '', session_id) + if _pid: + _p = await sor.sqlExe( + "SELECT default_model FROM sd_projects WHERE id=${p}$", {"p": _pid}) + await sor.sqlExe("COMMIT", {}) + if _p: + project_model = getattr(_p[0], 'default_model', '') or '' + except Exception as e: + debug(f'agent_model_options project_model error: {e}') + + # 个人之前选择的默认模型(项目未设模型时的回退选中项) current_llm_id = '' if uid: _s = await sor.sqlExe( @@ -32,7 +50,7 @@ for r in recs: 'provider': r.provider, 'model_id': r.model_id, 'capabilities': r.capabilities or 'text', - 'selected': r.id == current_llm_id, + 'selected': (r.name == project_model) if project_model else (r.id == current_llm_id), }) return rows