Skip to content

Commit 7aae3c4

Browse files
committed
✨Agent configuration page logic optimization #1711
[Specification Detail] 1.Modify the method in the backend /agent/list API to use the is_available field to indicate whether the agent is available, and the unavailable_reasons field to record the reasons for unavailability (including model unavailable, tool unavailable, or duplicate agent name) 2.Modify the front-end error message logic. Remove tooltip notifications, and display messages in the tool list and Agent details section. For existing Agents, error information is obtained from the backend, while real-time checks are performed during editing and creation.
1 parent cb7c776 commit 7aae3c4

File tree

10 files changed

+389
-114
lines changed

10 files changed

+389
-114
lines changed

backend/services/agent_service.py

Lines changed: 118 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import uuid
66
from collections import deque
7-
from typing import Optional
7+
from typing import Optional, Dict
88

99
from fastapi import Header, Request
1010
from fastapi.responses import JSONResponse, StreamingResponse
@@ -23,7 +23,7 @@
2323
ExportAndImportDataFormat,
2424
MCPInfo,
2525
ToolInstanceInfoRequest,
26-
ToolSourceEnum
26+
ToolSourceEnum, ModelConnectStatusEnum
2727
)
2828
from database.agent_db import (
2929
create_agent,
@@ -801,29 +801,140 @@ async def list_all_agent_info_impl(tenant_id: str) -> list[dict]:
801801
try:
802802
agent_list = query_all_agent_info_by_tenant_id(tenant_id=tenant_id)
803803

804-
simple_agent_list = []
804+
model_cache: Dict[int, Optional[dict]] = {}
805+
enriched_agents: list[dict] = []
806+
805807
for agent in agent_list:
806-
# check agent is available
807808
if not agent["enabled"]:
808809
continue
810+
811+
unavailable_reasons: list[str] = []
812+
809813
tool_info = search_tools_for_sub_agent(
810814
agent_id=agent["agent_id"], tenant_id=tenant_id)
811-
tool_id_list = [tool["tool_id"] for tool in tool_info]
812-
is_available = all(check_tool_is_available(tool_id_list))
815+
tool_id_list = [tool["tool_id"]
816+
for tool in tool_info if tool.get("tool_id") is not None]
817+
if tool_id_list:
818+
tool_statuses = check_tool_is_available(tool_id_list)
819+
if not all(tool_statuses):
820+
unavailable_reasons.append("tool_unavailable")
821+
822+
model_reasons = _collect_model_availability_reasons(
823+
agent=agent,
824+
tenant_id=tenant_id,
825+
model_cache=model_cache
826+
)
827+
unavailable_reasons.extend(model_reasons)
828+
829+
# Preserve the raw data so we can adjust availability for duplicates
830+
enriched_agents.append({
831+
"raw_agent": agent,
832+
"unavailable_reasons": unavailable_reasons,
833+
})
834+
835+
# Handle duplicate name/display_name: keep the earliest created agent available,
836+
# mark later ones as unavailable due to duplication.
837+
_apply_duplicate_name_availability_rules(enriched_agents)
838+
839+
simple_agent_list: list[dict] = []
840+
for entry in enriched_agents:
841+
agent = entry["raw_agent"]
842+
unavailable_reasons = list(dict.fromkeys(entry["unavailable_reasons"]))
813843

814844
simple_agent_list.append({
815845
"agent_id": agent["agent_id"],
816846
"name": agent["name"] if agent["name"] else agent["display_name"],
817847
"display_name": agent["display_name"] if agent["display_name"] else agent["name"],
818848
"description": agent["description"],
819-
"is_available": is_available
849+
"is_available": len(unavailable_reasons) == 0,
850+
"unavailable_reasons": unavailable_reasons
820851
})
852+
821853
return simple_agent_list
822854
except Exception as e:
823855
logger.error(f"Failed to query all agent info: {str(e)}")
824856
raise ValueError(f"Failed to query all agent info: {str(e)}")
825857

826858

859+
def _apply_duplicate_name_availability_rules(enriched_agents: list[dict]) -> None:
860+
"""
861+
For agents that share the same name or display_name, only the earliest created
862+
agent should remain available (if it has no other unavailable reasons).
863+
All later-created agents in the same group become unavailable due to duplication.
864+
"""
865+
# Group by name and display_name
866+
name_groups: dict[str, list[dict]] = {}
867+
display_name_groups: dict[str, list[dict]] = {}
868+
869+
for entry in enriched_agents:
870+
agent = entry["raw_agent"]
871+
name = agent.get("name")
872+
if name:
873+
name_groups.setdefault(name, []).append(entry)
874+
875+
display_name = agent.get("display_name")
876+
if display_name:
877+
display_name_groups.setdefault(display_name, []).append(entry)
878+
879+
def _mark_duplicates(groups: dict[str, list[dict]], reason_key: str) -> None:
880+
for entries in groups.values():
881+
if len(entries) <= 1:
882+
continue
883+
884+
# Sort by create_time ascending so the earliest created agent comes first
885+
sorted_entries = sorted(
886+
entries,
887+
key=lambda e: e["raw_agent"].get("create_time"),
888+
)
889+
890+
# The first (earliest) agent keeps its current availability;
891+
# subsequent agents are marked as duplicates.
892+
for duplicate_entry in sorted_entries[1:]:
893+
duplicate_entry["unavailable_reasons"].append(reason_key)
894+
895+
_mark_duplicates(name_groups, "duplicate_name")
896+
_mark_duplicates(display_name_groups, "duplicate_display_name")
897+
898+
899+
def _collect_model_availability_reasons(agent: dict, tenant_id: str, model_cache: Dict[int, Optional[dict]]) -> list[str]:
900+
"""
901+
Build a list of reasons related to model availability issues for a given agent.
902+
"""
903+
reasons: list[str] = []
904+
reasons.extend(_check_single_model_availability(
905+
model_id=agent.get("model_id"),
906+
tenant_id=tenant_id,
907+
model_cache=model_cache,
908+
reason_key="model_unavailable"
909+
))
910+
911+
return reasons
912+
913+
914+
def _check_single_model_availability(
915+
model_id: int | None,
916+
tenant_id: str,
917+
model_cache: Dict[int, Optional[dict]],
918+
reason_key: str,
919+
) -> list[str]:
920+
if not model_id:
921+
return []
922+
923+
if model_id not in model_cache:
924+
model_cache[model_id] = get_model_by_model_id(model_id, tenant_id)
925+
926+
model_info = model_cache.get(model_id)
927+
if not model_info:
928+
return [reason_key]
929+
930+
connect_status = ModelConnectStatusEnum.get_value(
931+
model_info.get("connect_status"))
932+
if connect_status != ModelConnectStatusEnum.AVAILABLE.value:
933+
return [reason_key]
934+
935+
return []
936+
937+
827938
def insert_related_agent_impl(parent_agent_id, child_agent_id, tenant_id):
828939
# search the agent by bfs, check if there is a circular call
829940
search_list = deque([child_agent_id])

frontend/app/[locale]/setup/agents/components/AgentSetupOrchestrator.tsx

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,38 @@ export default function AgentSetupOrchestrator({
113113
// Edit agent related status
114114
const [isEditingAgent, setIsEditingAgent] = useState(false);
115115
const [editingAgent, setEditingAgent] = useState<Agent | null>(null);
116+
const activeEditingAgent = editingAgentFromParent || editingAgent;
117+
const isAgentUnavailable = activeEditingAgent?.is_available === false;
118+
const agentUnavailableReasons =
119+
isAgentUnavailable && Array.isArray(activeEditingAgent?.unavailable_reasons)
120+
? (activeEditingAgent?.unavailable_reasons as string[])
121+
: [];
122+
const mergeAgentAvailabilityMetadata = useCallback(
123+
(detail: Agent, fallback?: Agent | null): Agent => {
124+
const detailReasons = Array.isArray(detail?.unavailable_reasons)
125+
? detail.unavailable_reasons
126+
: [];
127+
const fallbackReasons = Array.isArray(fallback?.unavailable_reasons)
128+
? fallback!.unavailable_reasons!
129+
: [];
130+
const normalizedReasons =
131+
detailReasons.length > 0 ? detailReasons : fallbackReasons;
132+
133+
const normalizedAvailability =
134+
typeof detail?.is_available === "boolean"
135+
? detail.is_available
136+
: typeof fallback?.is_available === "boolean"
137+
? fallback.is_available
138+
: detail?.is_available;
139+
140+
return {
141+
...detail,
142+
unavailable_reasons: normalizedReasons,
143+
is_available: normalizedAvailability,
144+
};
145+
},
146+
[]
147+
);
116148

117149
// Add a flag to track if it has been initialized to avoid duplicate calls
118150
const hasInitialized = useRef(false);
@@ -287,7 +319,11 @@ export default function AgentSetupOrchestrator({
287319
return;
288320
}
289321

290-
const agentDetail = result.data;
322+
const agentDetail = mergeAgentAvailabilityMetadata(
323+
result.data as Agent,
324+
editingAgent
325+
);
326+
setEditingAgent(agentDetail);
291327

292328
// Reload all agent data to match backend state
293329
setAgentName?.(agentDetail.name || "");
@@ -296,7 +332,7 @@ export default function AgentSetupOrchestrator({
296332

297333
// Load Agent data to interface
298334
setMainAgentModel(agentDetail.model);
299-
setMainAgentModelId(agentDetail.model_id);
335+
setMainAgentModelId(agentDetail.model_id ?? null);
300336
setMainAgentMaxStep(agentDetail.max_step);
301337
setBusinessLogic(agentDetail.business_description || "");
302338
setBusinessLogicModel(agentDetail.business_logic_model_name || null);
@@ -971,7 +1007,9 @@ export default function AgentSetupOrchestrator({
9711007
try {
9721008
const detail = await searchAgentInfo(newId);
9731009
if (detail.success && detail.data) {
974-
const agentDetail = detail.data;
1010+
const agentDetail = mergeAgentAvailabilityMetadata(
1011+
detail.data as Agent
1012+
);
9751013
setIsEditingAgent(true);
9761014
setEditingAgent(agentDetail);
9771015
setMainAgentId(agentDetail.id);
@@ -982,7 +1020,7 @@ export default function AgentSetupOrchestrator({
9821020
setAgentDisplayName?.(agentDetail.display_name || "");
9831021
onEditingStateChange?.(true, agentDetail);
9841022
setMainAgentModel(agentDetail.model);
985-
setMainAgentModelId(agentDetail.model_id);
1023+
setMainAgentModelId(agentDetail.model_id ?? null);
9861024
setMainAgentMaxStep(agentDetail.max_step);
9871025
setBusinessLogic(agentDetail.business_description || "");
9881026
setBusinessLogicModel(
@@ -1146,7 +1184,10 @@ export default function AgentSetupOrchestrator({
11461184
return;
11471185
}
11481186

1149-
const agentDetail = result.data;
1187+
const agentDetail = mergeAgentAvailabilityMetadata(
1188+
result.data as Agent,
1189+
agent
1190+
);
11501191

11511192
// Set editing state and highlight after successfully getting information
11521193
setIsEditingAgent(true);
@@ -1170,7 +1211,7 @@ export default function AgentSetupOrchestrator({
11701211

11711212
// Load Agent data to interface
11721213
setMainAgentModel(agentDetail.model);
1173-
setMainAgentModelId(agentDetail.model_id);
1214+
setMainAgentModelId(agentDetail.model_id ?? null);
11741215
setMainAgentMaxStep(agentDetail.max_step);
11751216
setBusinessLogic(agentDetail.business_description || "");
11761217
setBusinessLogicModel(agentDetail.business_logic_model_name || null);
@@ -1595,7 +1636,6 @@ export default function AgentSetupOrchestrator({
15951636
isGeneratingAgent={isGeneratingAgent}
15961637
editingAgent={editingAgent}
15971638
isCreatingNewAgent={isCreatingNewAgent}
1598-
editingAgentName={agentName || null}
15991639
onExportAgent={handleExportAgentFromList}
16001640
onDeleteAgent={handleDeleteAgentFromList}
16011641
unsavedAgentId={
@@ -1682,6 +1722,7 @@ export default function AgentSetupOrchestrator({
16821722
isEditingMode={isEditingAgent || isCreatingNewAgent}
16831723
isGeneratingAgent={isGeneratingAgent}
16841724
isEmbeddingConfigured={isEmbeddingConfigured}
1725+
agentUnavailableReasons={agentUnavailableReasons}
16851726
/>
16861727
</div>
16871728
</div>

0 commit comments

Comments
 (0)