diff --git a/pipeline_service/agent_loop.py b/pipeline_service/agent_loop.py index 8694be4..d13f470 100644 --- a/pipeline_service/agent_loop.py +++ b/pipeline_service/agent_loop.py @@ -99,28 +99,47 @@ async def _resolve_role(project_id, role): async def _resolve_llm_context(sor, project_id, role, model_name=None): """解析角色 agent 的 LLM 上下文:返回 (model_name, org_id)。 - model_name 解析链(与会话 agent 的 load_agent_config 一致): + model_name 解析链: 1. 显式传入的 model_name - 2. RoleSpec.model_name(角色专属模型) - 3. 产线 default_model(pipelines.default_model) - 4. 全局默认 "deepseek-v4-pro" + 2. 项目-角色-模型(sd_project_role_models,设置界面配的) + 3. 用户当前模型(项目创建者 created_by → pipeline_agent_settings.default_llm_id → llm.name,缺省) + 4. RoleSpec.model_name(角色专属模型) + 5. 产线 default_model(pipelines.default_model) + 6. 全局默认 "deepseek-v4-pro" org_id:从 sd_projects.org_id 读,传给 llm_bridge 做多租户隔离 (llm 表查询只取「本机构 + 系统级 org_id='0'」的模型)。 """ org_id = "" pipeline_id = "" + default_model = "" try: recs = await sor.sqlExe( - "SELECT org_id, pipeline_id FROM sd_projects WHERE id=${pid}$", {"pid": project_id}) + "SELECT org_id, pipeline_id, default_model FROM sd_projects WHERE id=${pid}$", {"pid": project_id}) if recs: org_id = getattr(recs[0], "org_id", "") or "" pipeline_id = getattr(recs[0], "pipeline_id", "") or "" + default_model = getattr(recs[0], "default_model", "") or "" except Exception: pass + # 2. 项目-角色-模型(设置界面配的,最高优先) + if not model_name and project_id: + try: + recs = await sor.sqlExe( + "SELECT model_name FROM sd_project_role_models WHERE project_id=${pid}$ AND role=${r}$", + {"pid": project_id, "r": role}) + if recs and getattr(recs[0], "model_name", ""): + model_name = getattr(recs[0], "model_name", "") + except Exception: + pass + + # 3. 项目缺省模型(创建项目时记录的「当前会话模型」,session 级缺省) + if not model_name and default_model: + model_name = default_model + if not model_name: - # 2. RoleSpec.model_name(角色专属模型) + # 4. RoleSpec.model_name(角色专属模型) try: from pipeline_core import get_role_spec spec = get_role_spec(pipeline_id or await _resolve_pipeline_id(project_id), role) @@ -129,7 +148,7 @@ async def _resolve_llm_context(sor, project_id, role, model_name=None): except Exception: pass if not model_name and pipeline_id: - # 3. 产线 default_model + # 5. 产线 default_model try: recs = await sor.sqlExe( "SELECT default_model FROM pipelines WHERE id=${pid}$", {"pid": pipeline_id}) @@ -138,7 +157,7 @@ async def _resolve_llm_context(sor, project_id, role, model_name=None): except Exception: pass if not model_name: - # 4. 全局默认 + # 6. 全局默认 model_name = "deepseek-v4-pro" return model_name, org_id diff --git a/pipeline_service/agent_loop_v2.py b/pipeline_service/agent_loop_v2.py index f15bcfa..11a4d8c 100644 --- a/pipeline_service/agent_loop_v2.py +++ b/pipeline_service/agent_loop_v2.py @@ -860,6 +860,8 @@ class AgentExecutor: "id": pid_val, "name": name, "description": desc, "status": "active", "org_id": org_id, "pipeline_id": self.space, "workspace_dir": workspace_dir, + "created_by": self.user_id or "", + "default_model": self.model_name or "", }) await sor.C("sd_iterations", { "id": getID(), "project_id": pid_val, diff --git a/pipeline_service/sdlc_ability.py b/pipeline_service/sdlc_ability.py index cc116d4..b26f644 100644 --- a/pipeline_service/sdlc_ability.py +++ b/pipeline_service/sdlc_ability.py @@ -178,6 +178,8 @@ SDL_TOOLS = [ ), # ── 项目生命周期 ── ToolDefinition(name="list_projects", description="列出项目列表(含状态)", parameters={"status": "按状态筛选(可选)"}, category="project"), + ToolDefinition(name="list_role_models", description="列出项目各角色的模型配置(缺省用项目缺省模型)", parameters={"project_id": "项目ID"}, category="project"), + ToolDefinition(name="set_role_model", description="设置某角色的模型(如 deploy_test 用 qwen3.8-max;model_name 空=清除回退缺省)", parameters={"project_id": "项目ID", "role": "角色名(agent.develop/agent.deploy_test等)", "model_name": "模型名(llm.name,空=回退缺省)"}, category="project"), ToolDefinition(name="start_project", description="启动项目(draft/archived→active)", parameters={"project_id": "项目ID"}, category="project"), ToolDefinition(name="complete_project", description="完成项目(active→completed)", parameters={"project_id": "项目ID"}, category="project"), ToolDefinition(name="archive_project", description="归档项目(completed→archived)", parameters={"project_id": "项目ID"}, category="project"), @@ -1168,6 +1170,51 @@ async def _h_start_project(sor, p, ctx): return f"OK: {msg}" if ok else f"ERROR: {msg}" +async def _h_list_role_models(sor, p, ctx): + """列出项目各角色的模型配置(sd_project_role_models)。""" + pid = (p.get("project_id", "") or "").strip() or (ctx.get("project_id", "") or "") + if not pid: + return "需要 project_id" + pid = await _resolve_id(sor, "sd_projects", pid) or pid + recs = await sor.sqlExe( + "SELECT role, model_name FROM sd_project_role_models WHERE project_id=${pid}$ ORDER BY role", + {"pid": pid}) + await sor.sqlExe("COMMIT", {}) + if not recs: + return "(无角色模型配置,所有角色用项目缺省模型)" + lines = [] + for r in recs: + lines.append(f"- {r.role} -> {r.model_name or '(空=缺省)'}") + return "\n".join(lines) + + +async def _h_set_role_model(sor, p, ctx): + """设置某角色的模型(model_name 空 = 清除,回退项目缺省模型)。""" + pid = (p.get("project_id", "") or "").strip() or (ctx.get("project_id", "") or "") + role = (p.get("role", "") or "").strip() + model_name = (p.get("model_name", "") or "").strip() + if not pid: + return "需要 project_id" + if not role: + return "需要 role" + pid = await _resolve_id(sor, "sd_projects", pid) or pid + from appPublic.uniqueID import getID + recs = await sor.sqlExe( + "SELECT id FROM sd_project_role_models WHERE project_id=${pid}$ AND role=${r}$", + {"pid": pid, "r": role}) + await sor.sqlExe("COMMIT", {}) + if recs: + await sor.sqlExe( + "UPDATE sd_project_role_models SET model_name=${m}$, updated_at=NOW() WHERE id=${id}$", + {"m": model_name or None, "id": getattr(recs[0], "id", "")}) + else: + await sor.C("sd_project_role_models", { + "id": getID(), "project_id": pid, "role": role, "model_name": model_name or None, + }) + await sor.sqlExe("COMMIT", {}) + return f"OK: {role} 模型 -> {model_name or '(空=用项目缺省模型)'}" + + async def _h_complete_project(sor, p, ctx): pid = (p.get("project_id", "") or "").strip() if not pid: @@ -1760,6 +1807,8 @@ SDL_HANDLERS = { "verify_feature": _h_verify_feature, # ── 项目生命周期 ── "list_projects": _h_list_projects, + "list_role_models": _h_list_role_models, + "set_role_model": _h_set_role_model, "start_project": _h_start_project, "complete_project": _h_complete_project, "archive_project": _h_archive_project,