feat: Provider model_settings 全链路 + 监管节点工具集 + 重型插件注入 + 前端打磨

- Provider model_settings (Provider+Model 级别参数配置): DB JSONB → API → GSM → AgentFactory.resolve → 三节点 agent.run 注入
- 新增 data/toolset/regulatory_toolset/: 监管节点专属工具(query_workflow_status / query_task_list / send_file)
- send_file 从 interactive_toolset 迁移至 regulatory_toolset,interactive 仅保留 approval
- mcp_helper 合入 GlobalPluginManager dispatch tools
- 前端 Provider 弹窗参数设置区加 JSON 编辑器(model_settings)
- 前端 Plugin 页面新增"重型插件"Tab(HeavyPluginList 占位)
- .gitignore 精简:去除系统默认项,修复 data/ 子目录追踪
- data/toolset/ 与 data/plugin/ 首次纳入版本控制

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 13:10:31 +00:00
parent 6d658b4f4d
commit 005ce566a8
49 changed files with 1093 additions and 30 deletions
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Sequence, Any
from typing import Sequence, Any, Dict
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
@@ -126,3 +126,19 @@ class AgentFactory:
toolsets=toolsets or [],
)
return agent
@staticmethod
def resolve_model_settings(provider: Provider, model_id: str) -> Dict[str, Any]:
"""合并 provider.model_settings 中 ``__default__`` 与具体 model_id 的参数。
- ``__default__`` 是全 Provider 的兜底参数
- ``model_id`` 键覆盖 default 中相同 key
- 都缺省时返回空 dict``agent.run(model_settings={})`` 等效于不传)
"""
settings = getattr(provider, "model_settings", None) or {}
if not isinstance(settings, dict):
return {}
default = settings.get("__default__", {}) or {}
specific = settings.get(model_id, {}) or {}
merged: Dict[str, Any] = {**default, **specific}
return merged
+4 -2
View File
@@ -13,8 +13,8 @@
# limitations under the License.
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from typing import Any, Dict, Literal
from pydantic import BaseModel, Field
from typing import Any, Dict, Literal, Optional
from kilostar.utils.access import TokenData, Accessor, RoleChecker
from kilostar.core.postgres_database.model import UserAuthority
from kilostar.core.global_state_machine.model_provider.base_provider import Provider
@@ -30,6 +30,7 @@ class ProviderRegister(BaseModel):
provider_title: str
provider_url: str
provider_apikey: str
model_settings: Optional[Dict[str, Dict[str, Any]]] = Field(default=None)
@provider_router.post("")
@@ -45,6 +46,7 @@ async def create_provider(
provider_url=provider_register.provider_url,
provider_apikey=provider_register.provider_apikey,
provider_owner=token_data.user_id,
model_settings=provider_register.model_settings or {},
)
@@ -184,6 +184,7 @@ class GlobalStateMachine:
provider_url,
provider_apikey,
provider_owner,
model_settings=None,
):
"""新增一个模型 Provider:内存注册 + 数据库持久化一并完成。"""
result = await self._global_provider_manager.add_provider(
@@ -193,6 +194,7 @@ class GlobalStateMachine:
provider_apikey=provider_apikey,
provider_owner=provider_owner,
postgres_database=self.postgres_database,
model_settings=model_settings or {},
)
self._publish_snapshot()
return result
@@ -13,8 +13,8 @@
# limitations under the License.
from abc import ABC, abstractmethod
from pydantic import BaseModel
from typing import List
from pydantic import BaseModel, Field
from typing import Any, Dict, List
from enum import Enum
@@ -35,6 +35,7 @@ class Provider(BaseModel):
provider_type: str
provider_owner: str | None = None
provider_status: ProviderStatus = ProviderStatus.UP
model_settings: Dict[str, Dict[str, Any]] = Field(default_factory=dict)
class ProviderArgs(BaseModel):
@@ -44,6 +45,7 @@ class ProviderArgs(BaseModel):
provider_url: str
provider_apikey: str
provider_owner: str
model_settings: Dict[str, Dict[str, Any]] = Field(default_factory=dict)
class BaseProvider(ABC):
@@ -78,4 +78,5 @@ class ClaudeProvider(BaseProvider):
provider_url=provider_args.provider_url,
provider_models=provider_models,
provider_type="claude",
model_settings=provider_args.model_settings,
)
@@ -81,4 +81,5 @@ class DeepseekProvider(BaseProvider):
provider_url=provider_args.provider_url,
provider_models=provider_models,
provider_type="deepseek",
model_settings=provider_args.model_settings,
)
@@ -78,4 +78,5 @@ class GeminiProvider(BaseProvider):
provider_url=provider_args.provider_url,
provider_models=provider_models,
provider_type="gemini",
model_settings=provider_args.model_settings,
)
@@ -81,4 +81,5 @@ class OpenAIProvider(BaseProvider):
provider_url=provider_args.provider_url,
provider_models=provider_models,
provider_type="openai",
model_settings=provider_args.model_settings,
)
@@ -58,6 +58,7 @@ class ProviderManager:
provider_apikey,
provider_owner,
postgres_database,
model_settings=None,
) -> None:
"""新增并落库一个 Provider
@@ -77,6 +78,7 @@ class ProviderManager:
provider_url=provider_url,
provider_apikey=provider_apikey,
provider_owner=provider_owner,
model_settings=model_settings or {},
)
try:
import ulid
@@ -96,6 +98,7 @@ class ProviderManager:
provider_models=provider.provider_models,
provider_type=provider.provider_type,
provider_owner=provider.provider_owner,
model_settings=provider.model_settings,
)
logger.info(f"已添加适配器{provider_title}")
@@ -40,6 +40,7 @@ class ConsciousnessNode:
self.logger = get_logger("consciousness_node")
self.agent: None | Agent = None
self.locale: str = "zh"
self._model_settings: dict = {}
async def create_agent(
self,
@@ -73,6 +74,7 @@ class ConsciousnessNode:
tools=tools,
toolsets=toolsets,
)
self._model_settings = AgentFactory.resolve_model_settings(provider, model_id)
@self.agent.system_prompt
async def dynamic_prompt(ctx: RunContext[ConsciousnessNodeDeps]):
@@ -221,7 +223,7 @@ class ConsciousnessNode:
)
self.logger.debug("ConsciousnessNode: 开始生成工作流 (原生重试开启)")
prompt = "根据original_command制定严密的可执行workflow"
result = await self.agent.run(prompt, deps=deps)
result = await self.agent.run(prompt, deps=deps, model_settings=self._model_settings or None)
return result.output
elif isinstance(payload, ForWorkflowInput):
@@ -236,6 +238,7 @@ class ConsciousnessNode:
result = await self.agent.run(
f"处理此工作流步骤信息:\n{payload.workflow_step.model_dump_json()}",
deps=deps,
model_settings=self._model_settings or None,
)
return result.output
@@ -251,6 +254,7 @@ class ConsciousnessNode:
result = await self.agent.run(
f"基于以下工作流的执行记录,生成技术报告:\n{payload.workflow.model_dump_json()}",
deps=deps,
model_settings=self._model_settings or None,
)
return result.output
except Exception as e:
@@ -38,6 +38,7 @@ class ControlNode:
self.logger = get_logger("control_node")
self.agent: Agent | None = None
self._model_settings: dict = {}
async def create_agent(
self,
@@ -87,6 +88,7 @@ class ControlNode:
tools=callables,
toolsets=toolsets,
)
self._model_settings = AgentFactory.resolve_model_settings(provider, model_id)
@self.agent.system_prompt
async def dynamic_prompt(ctx: RunContext[ControlNodeDeps]):
@@ -121,6 +123,7 @@ class ControlNode:
result = await self.agent.run(
f"请根据提供的 workflow_step 上下文,执行此步骤并输出结果。\n详细指令或附加数据:{payload.workflow_step.model_dump_json()}",
deps=deps,
model_settings=self._model_settings or None,
)
return result.output
except Exception as e:
@@ -40,6 +40,7 @@ class RegulatoryNode:
self.logger = get_logger("regulatory_node")
self.agent: None | Agent = None
self._model_settings: dict = {}
async def create_agent(
self,
@@ -88,6 +89,7 @@ class RegulatoryNode:
tools=tools,
toolsets=toolsets,
)
self._model_settings = AgentFactory.resolve_model_settings(provider, model_id)
@self.agent.system_prompt
async def dynamic_prompt(ctx: RunContext[RegulatoryNodeDeps]):
@@ -178,6 +180,7 @@ class RegulatoryNode:
instructions=self._CHAT_INSTRUCTIONS,
event_stream_handler=_stream_handler,
message_history=message_history,
model_settings=self._model_settings or None,
)
except Exception as e:
self.logger.exception(f"RegulatoryNode.stream_working failed: {e}")
@@ -204,6 +207,7 @@ class RegulatoryNode:
user_prompt=message,
deps=deps,
message_history=message_history,
model_settings=self._model_settings or None,
)
response: MessageResponse = agent_response.output
response.platform = platform
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List, Optional
from typing import Any, Dict, List, Optional
from sqlalchemy import String, Text, Boolean, text
from sqlalchemy.dialects.postgresql import JSONB # 针对供应商模型列表优化
from sqlalchemy.orm import Mapped, mapped_column
@@ -41,3 +41,8 @@ class ProviderModel(BaseDataModel):
server_default=text("true"),
comment="该服务商节点是否在线/启用",
)
model_settings: Mapped[Optional[Dict[str, Any]]] = mapped_column(
JSONB,
nullable=True,
comment="模型调用参数:{model_id 或 __default__: ModelSettings dict}",
)
@@ -69,6 +69,7 @@ class ProviderDatabase:
provider_type=provider.provider_type,
provider_owner=provider.provider_owner,
is_active=provider.is_active,
model_settings=provider.model_settings,
)
for provider in results
]
+11
View File
@@ -17,6 +17,7 @@
from typing import Dict, List, Any, Optional, Sequence
from kilostar.utils.logger import get_logger
from kilostar.utils.ray_hook import ray_actor_hook
logger = get_logger("mcp_helper")
@@ -128,6 +129,16 @@ async def get_all_tools_and_toolsets_for_scope(
logger.error(f"Failed to load tools from GSM ({scope}): {e}")
toolsets = await get_mcp_toolsets_from_gsm()
# 合入重型插件的 dispatch tools
try:
pm = ray_actor_hook("global_plugin_manager").global_plugin_manager
dispatch = await pm.get_dispatch_tools.remote()
if dispatch:
tools.extend(dispatch.values())
except Exception as e:
logger.debug(f"No dispatch tools available: {e}")
return tools, toolsets