Add Agent capabilities and configurations across the application

- Extend the AssistantConfig model to include agent_interface_type, agent_values, and agent_secrets for enhanced agent management.
- Update ModelType to include "Agent" for broader capability support.
- Seed new agent models and configurations in the database for Dify, FastGPT, and OpenCode applications.
- Modify the Assistant routes to validate and handle agent capabilities.
- Implement agent resource resolution in the config resolver for runtime configuration.
- Enhance the interface catalog with agent-specific fields and definitions.
- Update frontend components to manage agent configurations, including form handling and state management for agent-related inputs.
This commit is contained in:
Xin Wang
2026-07-09 15:29:25 +08:00
parent 03478fa557
commit 54329aa516
11 changed files with 238 additions and 114 deletions

View File

@@ -39,10 +39,13 @@ VALUES
('asst_002', 'TTS', 'model_004', '{}'),
('asst_003', 'ASR', 'model_002', '{}'),
('asst_003', 'TTS', 'model_004', '{}'),
('asst_003', 'Agent', 'model_014', '{}'),
('asst_004', 'ASR', 'model_002', '{}'),
('asst_004', 'TTS', 'model_004', '{}'),
('asst_004', 'Agent', 'model_015', '{}'),
('asst_005', 'ASR', 'model_002', '{}'),
('asst_005', 'TTS', 'model_004', '{}')
('asst_005', 'TTS', 'model_004', '{}'),
('asst_005', 'Agent', 'model_016', '{}')
ON CONFLICT (assistant_id, capability) DO UPDATE SET
model_resource_id = EXCLUDED.model_resource_id,
updated_at = now();

View File

@@ -40,6 +40,22 @@ VALUES
{"key":"apiKey","label":"API Key","group":"secrets","type":"password","required":true},
{"key":"voice","label":"Voice","group":"values","type":"text","required":false}
]} $$::jsonb, TRUE, 1),
('dify', 'Dify Agent', 'Agent',
$$ {"fields":[
{"key":"apiUrl","label":"API URL","group":"values","type":"url","required":true},
{"key":"apiKey","label":"API Key","group":"secrets","type":"password","required":true}
]} $$::jsonb, TRUE, 1),
('fastgpt', 'FastGPT Agent', 'Agent',
$$ {"fields":[
{"key":"apiUrl","label":"API URL","group":"values","type":"url","required":true},
{"key":"apiKey","label":"API Key","group":"secrets","type":"password","required":true},
{"key":"appId","label":"App ID","group":"values","type":"text","required":true}
]} $$::jsonb, TRUE, 1),
('opencode', 'OpenCode Agent', 'Agent',
$$ {"fields":[
{"key":"apiUrl","label":"API URL","group":"values","type":"url","required":true},
{"key":"apiKey","label":"API Key","group":"secrets","type":"password","required":true}
]} $$::jsonb, TRUE, 1),
('stepfun-realtime', 'StepFun StepAudio Realtime', 'Realtime',
$$ {"fields":[
{"key":"modelId","label":"Model ID","group":"values","type":"text","required":true},

View File

@@ -41,6 +41,15 @@ VALUES
'{"apiKey":"replace-me"}', FALSE, TRUE, FALSE),
('model_013', 'doubao-seed-2-0-mini-260428', 'LLM', 'openai-llm',
'{"modelId":"doubao-seed-2-0-mini-260428","apiUrl":"https://ark.cn-beijing.volces.com/api/v3","temperature":0.7,"extraBody":{"thinking":{"type":"disabled"}}}',
'{"apiKey":"replace-me"}', TRUE, TRUE, FALSE)
'{"apiKey":"replace-me"}', TRUE, TRUE, FALSE),
('model_014', 'Dify 客服应用', 'Agent', 'dify',
'{"apiUrl":"https://api.dify.ai/v1/chat-messages"}',
'{"apiKey":"replace-me"}', FALSE, TRUE, FALSE),
('model_015', 'FastGPT 售后应用', 'Agent', 'fastgpt',
'{"apiUrl":"https://api.fastgpt.in/api/v1/chat/completions","appId":"app-fastgpt-001"}',
'{"apiKey":"replace-me"}', FALSE, TRUE, FALSE),
('model_016', 'OpenCode 服务', 'Agent', 'opencode',
'{"apiUrl":"http://localhost:4096"}',
'{"apiKey":"replace-me"}', FALSE, TRUE, FALSE)
-- Seed defaults must never overwrite resources configured through the UI.
ON CONFLICT (id) DO NOTHING;

View File

@@ -36,6 +36,9 @@ class AssistantConfig(BaseModel):
realtime_interface_type: str = ""
realtime_values: dict = {}
realtime_secrets: dict = {}
agent_interface_type: str = ""
agent_values: dict = {}
agent_secrets: dict = {}
llm_interface_type: str = "openai-llm"
stt_interface_type: str = "openai-asr"
tts_interface_type: str = "openai-tts"

View File

@@ -12,7 +12,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
router = APIRouter(prefix="/api/assistants", tags=["assistants"])
CAPABILITIES = ("LLM", "ASR", "TTS", "Realtime", "Embedding")
CAPABILITIES = ("LLM", "ASR", "TTS", "Realtime", "Embedding", "Agent")
def _validate_workflow(body: AssistantUpsert) -> None:

View File

@@ -13,7 +13,7 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator
from pydantic.alias_generators import to_camel
RuntimeMode = Literal["pipeline", "realtime"]
ModelType = Literal["LLM", "ASR", "TTS", "Realtime", "Embedding"]
ModelType = Literal["LLM", "ASR", "TTS", "Realtime", "Embedding", "Agent"]
AssistantType = Literal["prompt", "workflow", "dify", "fastgpt", "opencode"]
# 外部应用类型:其 config.apiKey 是该助手私有密钥,读时打码 / 写时哨兵

View File

@@ -33,6 +33,36 @@ async def _resource_for(
return resource
async def _agent_resource_for(
session: AsyncSession,
assistant_id: str,
interface_type: str,
) -> ModelResource | None:
if interface_type not in {"dify", "fastgpt", "opencode"}:
return None
binding = await session.get(AssistantModelBinding, (assistant_id, "Agent"))
resource_id = binding.model_resource_id if binding else None
resource = await session.get(ModelResource, resource_id) if resource_id else None
if (
resource
and resource.capability == "Agent"
and resource.interface_type == interface_type
):
return resource
return (
await session.execute(
select(ModelResource)
.where(
ModelResource.capability == "Agent",
ModelResource.interface_type == interface_type,
ModelResource.enabled.is_(True),
)
.order_by(ModelResource.is_default.desc(), ModelResource.id.asc())
.limit(1)
)
).scalar_one_or_none()
def _value(resource: ModelResource | None, key: str, default):
if not resource:
return default
@@ -58,6 +88,7 @@ async def resolve_runtime_config(
stt_resource = await _resource_for(session, assistant.id, "ASR")
tts_resource = await _resource_for(session, assistant.id, "TTS")
realtime_resource = await _resource_for(session, assistant.id, "Realtime")
agent_resource = await _agent_resource_for(session, assistant.id, assistant.type)
vision_resource = (
await session.get(ModelResource, assistant.vision_model_resource_id)
if assistant.vision_model_resource_id
@@ -75,9 +106,9 @@ async def resolve_runtime_config(
# workflow 图:仅 workflow 类型非空,引擎据此启用图驱动对话
graph=(assistant.graph or {}) if assistant.type == "workflow" else {},
# 外部托管类型连接信息(DB 存真 key,直接注入)
fastgpt_api_url=assistant.api_url,
fastgpt_api_key=assistant.api_key,
fastgpt_app_id=assistant.app_id,
fastgpt_api_url=str(_value(agent_resource, "apiUrl", assistant.api_url)),
fastgpt_api_key=_secret(agent_resource, "apiKey", assistant.api_key),
fastgpt_app_id=str(_value(agent_resource, "appId", assistant.app_id)),
# 模型/音色:模型资源中的配置优先
model=str(_value(llm_resource, "modelId", "")),
asr=str(_value(stt_resource, "modelId", "")),
@@ -114,6 +145,9 @@ async def resolve_runtime_config(
),
realtime_values=(realtime_resource.values or {}) if realtime_resource else {},
realtime_secrets=(realtime_resource.secrets or {}) if realtime_resource else {},
agent_interface_type=(agent_resource.interface_type if agent_resource else ""),
agent_values=(agent_resource.values or {}) if agent_resource else {},
agent_secrets=(agent_resource.secrets or {}) if agent_resource else {},
# 运行时连接信息(真 key + url):模型资源优先,否则 .env 兜底
llm_api_key=_secret(llm_resource, "apiKey", config.LLM_API_KEY),
llm_base_url=str(_value(llm_resource, "apiUrl", config.LLM_BASE_URL)),

View File

@@ -34,6 +34,10 @@ OPENAI_COMMON = [
field("apiUrl", "API URL", type_="url", required=True),
field("apiKey", "API Key", group="secrets", type_="password", required=True),
]
AGENT_COMMON = [
field("apiUrl", "API URL", type_="url", required=True),
field("apiKey", "API Key", group="secrets", type_="password", required=True),
]
XFYUN_AUTH = [
field("apiUrl", "WebSocket URL", type_="url", required=True),
field("appId", "App ID", group="secrets", type_="password", required=True),
@@ -78,6 +82,24 @@ INTERFACE_DEFINITIONS: list[dict] = [
"capability": "Realtime",
"fields": OPENAI_COMMON + [field("voice", "Voice")],
},
{
"interface_type": "dify",
"name": "Dify Agent",
"capability": "Agent",
"fields": AGENT_COMMON,
},
{
"interface_type": "fastgpt",
"name": "FastGPT Agent",
"capability": "Agent",
"fields": AGENT_COMMON + [field("appId", "App ID", required=True)],
},
{
"interface_type": "opencode",
"name": "OpenCode Agent",
"capability": "Agent",
"fields": AGENT_COMMON,
},
{
"interface_type": "stepfun-realtime",
"name": "StepFun StepAudio Realtime",