Skip to content

Commit a6454f8

Browse files
authored
♻️ Move the logic of renaming the duplicate agents to frontend
2 parents 1833a33 + 4e309a1 commit a6454f8

File tree

11 files changed

+1388
-223
lines changed

11 files changed

+1388
-223
lines changed

backend/apps/agent_app.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
from fastapi import APIRouter, Body, Header, HTTPException, Request
66

7-
from consts.model import AgentRequest, AgentInfoRequest, AgentIDRequest, ConversationResponse, AgentImportRequest
7+
from consts.model import AgentRequest, AgentInfoRequest, AgentIDRequest, ConversationResponse, AgentImportRequest, AgentNameBatchCheckRequest, AgentNameBatchRegenerateRequest
88
from services.agent_service import (
99
get_agent_info_impl,
1010
get_creating_sub_agent_info_impl,
1111
update_agent_info_impl,
1212
delete_agent_impl,
1313
export_agent_impl,
1414
import_agent_impl,
15+
check_agent_name_conflict_batch_impl,
16+
regenerate_agent_name_batch_impl,
1517
list_all_agent_info_impl,
1618
run_agent_stream,
1719
stop_agent_tasks,
@@ -146,6 +148,36 @@ async def import_agent_api(request: AgentImportRequest, authorization: Optional[
146148
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent import error.")
147149

148150

151+
@agent_config_router.post("/check_name")
152+
async def check_agent_name_batch_api(request: AgentNameBatchCheckRequest, authorization: Optional[str] = Header(None)):
153+
"""
154+
Batch check whether agent name/display_name conflicts exist in the tenant.
155+
"""
156+
try:
157+
return await check_agent_name_conflict_batch_impl(request, authorization)
158+
except ValueError as e:
159+
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
160+
except Exception as e:
161+
logger.error(f"Agent name batch check error: {str(e)}")
162+
raise HTTPException(
163+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent name batch check error.")
164+
165+
166+
@agent_config_router.post("/regenerate_name")
167+
async def regenerate_agent_name_batch_api(request: AgentNameBatchRegenerateRequest, authorization: Optional[str] = Header(None)):
168+
"""
169+
Batch regenerate agent name/display_name using LLM or suffix fallback.
170+
"""
171+
try:
172+
return await regenerate_agent_name_batch_impl(request, authorization)
173+
except ValueError as e:
174+
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
175+
except Exception as e:
176+
logger.error(f"Agent name batch regenerate error: {str(e)}")
177+
raise HTTPException(
178+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent name batch regenerate error.")
179+
180+
149181
@agent_config_router.get("/list")
150182
async def list_all_agent_info_api(authorization: Optional[str] = Header(None), request: Request = None):
151183
"""

backend/consts/model.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,27 @@ class AgentImportRequest(BaseModel):
345345
force_import: bool = False
346346

347347

348+
class AgentNameBatchRegenerateItem(BaseModel):
349+
name: str
350+
display_name: Optional[str] = None
351+
task_description: Optional[str] = ""
352+
agent_id: Optional[int] = None
353+
354+
355+
class AgentNameBatchRegenerateRequest(BaseModel):
356+
items: List[AgentNameBatchRegenerateItem]
357+
358+
359+
class AgentNameBatchCheckItem(BaseModel):
360+
name: str
361+
display_name: Optional[str] = None
362+
agent_id: Optional[int] = None
363+
364+
365+
class AgentNameBatchCheckRequest(BaseModel):
366+
items: List[AgentNameBatchCheckItem]
367+
368+
348369
class ConvertStateRequest(BaseModel):
349370
"""Request schema for /tasks/convert_state endpoint"""
350371
process_state: str = ""

backend/services/agent_service.py

Lines changed: 146 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from consts.model import (
2121
AgentInfoRequest,
2222
AgentRequest,
23+
AgentNameBatchCheckRequest,
24+
AgentNameBatchRegenerateRequest,
2325
ExportAndImportAgentInfo,
2426
ExportAndImportDataFormat,
2527
MCPInfo,
@@ -410,6 +412,150 @@ def _regenerate_agent_display_name_with_llm(
410412

411413

412414

415+
async def check_agent_name_conflict_batch_impl(
416+
request: AgentNameBatchCheckRequest,
417+
authorization: str
418+
) -> list[dict]:
419+
"""
420+
Batch check name/display_name duplication for multiple agents.
421+
"""
422+
_, tenant_id, _ = get_current_user_info(authorization)
423+
agents_cache = query_all_agent_info_by_tenant_id(tenant_id)
424+
425+
results: list[dict] = []
426+
for item in request.items:
427+
if not item.name:
428+
results.append({
429+
"name_conflict": False,
430+
"display_name_conflict": False,
431+
"conflict_agents": []
432+
})
433+
continue
434+
435+
conflicts: list[dict] = []
436+
name_conflict = False
437+
display_name_conflict = False
438+
for agent in agents_cache:
439+
if item.agent_id and agent.get("agent_id") == item.agent_id:
440+
continue
441+
matches_name = item.name and agent.get("name") == item.name
442+
matches_display = item.display_name and agent.get(
443+
"display_name") == item.display_name
444+
if matches_name:
445+
name_conflict = True
446+
if matches_display:
447+
display_name_conflict = True
448+
if matches_name or matches_display:
449+
conflicts.append({
450+
"name": agent.get("name"),
451+
"display_name": agent.get("display_name"),
452+
})
453+
454+
results.append({
455+
"name_conflict": name_conflict,
456+
"display_name_conflict": display_name_conflict,
457+
"conflict_agents": conflicts
458+
})
459+
return results
460+
461+
462+
async def regenerate_agent_name_batch_impl(
463+
request: AgentNameBatchRegenerateRequest,
464+
authorization: str
465+
) -> list[dict]:
466+
"""
467+
Batch regenerate agent name/display_name with LLM (or suffix fallback).
468+
"""
469+
_, tenant_id, _ = get_current_user_info(authorization)
470+
agents_cache = query_all_agent_info_by_tenant_id(tenant_id)
471+
472+
existing_names = [agent.get("name") for agent in agents_cache if agent.get("name")]
473+
existing_display_names = [agent.get("display_name") for agent in agents_cache if agent.get("display_name")]
474+
475+
# Always use tenant quick-config LLM model
476+
quick_config_model = tenant_config_manager.get_model_config(
477+
key=MODEL_CONFIG_MAPPING["llm"],
478+
tenant_id=tenant_id
479+
)
480+
resolved_model_id = quick_config_model.get("model_id") if quick_config_model else None
481+
if not resolved_model_id:
482+
raise ValueError("No available model for regeneration. Please configure an LLM model first.")
483+
484+
results: list[dict] = []
485+
# Use local mutable caches to avoid regenerated duplicates in the same batch
486+
name_set = set(existing_names)
487+
display_name_set = set(existing_display_names)
488+
489+
for item in request.items:
490+
agent_name = item.name or ""
491+
agent_display_name = item.display_name or ""
492+
task_description = item.task_description or ""
493+
exclude_agent_id = item.agent_id
494+
495+
# Regenerate name if duplicate and non-empty
496+
if agent_name and _check_agent_name_duplicate(
497+
agent_name, tenant_id, agents_cache=agents_cache, exclude_agent_id=exclude_agent_id
498+
):
499+
try:
500+
agent_name = await asyncio.to_thread(
501+
_regenerate_agent_name_with_llm,
502+
original_name=agent_name,
503+
existing_names=list(name_set),
504+
task_description=task_description,
505+
model_id=resolved_model_id,
506+
tenant_id=tenant_id,
507+
language=LANGUAGE["ZH"],
508+
agents_cache=agents_cache,
509+
exclude_agent_id=exclude_agent_id
510+
)
511+
except Exception as e:
512+
logger.error(f"Failed to regenerate agent name with LLM: {str(e)}, using fallback")
513+
agent_name = _generate_unique_agent_name_with_suffix(
514+
agent_name,
515+
tenant_id=tenant_id,
516+
agents_cache=agents_cache,
517+
exclude_agent_id=exclude_agent_id
518+
)
519+
520+
# Regenerate display_name if duplicate and non-empty
521+
if agent_display_name and _check_agent_display_name_duplicate(
522+
agent_display_name, tenant_id, agents_cache=agents_cache, exclude_agent_id=exclude_agent_id
523+
):
524+
try:
525+
agent_display_name = await asyncio.to_thread(
526+
_regenerate_agent_display_name_with_llm,
527+
original_display_name=agent_display_name,
528+
existing_display_names=list(display_name_set),
529+
task_description=task_description,
530+
model_id=resolved_model_id,
531+
tenant_id=tenant_id,
532+
language=LANGUAGE["ZH"],
533+
agents_cache=agents_cache,
534+
exclude_agent_id=exclude_agent_id
535+
)
536+
except Exception as e:
537+
logger.error(f"Failed to regenerate agent display_name with LLM: {str(e)}, using fallback")
538+
agent_display_name = _generate_unique_display_name_with_suffix(
539+
agent_display_name,
540+
tenant_id=tenant_id,
541+
agents_cache=agents_cache,
542+
exclude_agent_id=exclude_agent_id
543+
)
544+
545+
# Track regenerated names to avoid duplicates within batch
546+
if agent_name:
547+
name_set.add(agent_name)
548+
if agent_display_name:
549+
display_name_set.add(agent_display_name)
550+
551+
results.append({
552+
"name": agent_name,
553+
"display_name": agent_display_name
554+
})
555+
556+
return results
557+
558+
413559
async def _stream_agent_chunks(
414560
agent_request: "AgentRequest",
415561
user_id: str,
@@ -1002,84 +1148,9 @@ async def import_agent_by_agent_id(
10021148
tenant_id=tenant_id
10031149
)
10041150

1005-
# Check for duplicate names and regenerate if needed (unless forced import)
10061151
agent_name = import_agent_info.name
10071152
agent_display_name = import_agent_info.display_name
10081153

1009-
# Get all existing agent names and display names for duplicate checking
1010-
all_agents = query_all_agent_info_by_tenant_id(tenant_id)
1011-
existing_names = [agent.get("name") for agent in all_agents if agent.get("name")]
1012-
existing_display_names = [agent.get("display_name") for agent in all_agents if agent.get("display_name")]
1013-
1014-
if not skip_duplicate_regeneration:
1015-
# Check and regenerate name if duplicate
1016-
if _check_agent_name_duplicate(agent_name, tenant_id, agents_cache=all_agents):
1017-
logger.info(f"Agent name '{agent_name}' already exists, regenerating with LLM")
1018-
# Get model for regeneration (use business_logic_model_id if available, otherwise use model_id)
1019-
regeneration_model_id = business_logic_model_id or model_id
1020-
if regeneration_model_id:
1021-
try:
1022-
# Offload blocking LLM regeneration to a thread to avoid blocking the event loop
1023-
agent_name = await asyncio.to_thread(
1024-
_regenerate_agent_name_with_llm,
1025-
original_name=agent_name,
1026-
existing_names=existing_names,
1027-
task_description=import_agent_info.business_description or import_agent_info.description or "",
1028-
model_id=regeneration_model_id,
1029-
tenant_id=tenant_id,
1030-
language=LANGUAGE["ZH"], # Default to Chinese, can be enhanced later
1031-
agents_cache=all_agents,
1032-
)
1033-
logger.info(f"Regenerated agent name: '{agent_name}'")
1034-
except Exception as e:
1035-
logger.error(f"Failed to regenerate agent name with LLM: {str(e)}, using fallback")
1036-
agent_name = _generate_unique_agent_name_with_suffix(
1037-
agent_name,
1038-
tenant_id=tenant_id,
1039-
agents_cache=all_agents
1040-
)
1041-
else:
1042-
logger.warning("No model available for regeneration, using fallback")
1043-
agent_name = _generate_unique_agent_name_with_suffix(
1044-
agent_name,
1045-
tenant_id=tenant_id,
1046-
agents_cache=all_agents
1047-
)
1048-
1049-
# Check and regenerate display_name if duplicate
1050-
if _check_agent_display_name_duplicate(agent_display_name, tenant_id, agents_cache=all_agents):
1051-
logger.info(f"Agent display_name '{agent_display_name}' already exists, regenerating with LLM")
1052-
# Get model for regeneration (use business_logic_model_id if available, otherwise use model_id)
1053-
regeneration_model_id = business_logic_model_id or model_id
1054-
if regeneration_model_id:
1055-
try:
1056-
# Offload blocking LLM regeneration to a thread to avoid blocking the event loop
1057-
agent_display_name = await asyncio.to_thread(
1058-
_regenerate_agent_display_name_with_llm,
1059-
original_display_name=agent_display_name,
1060-
existing_display_names=existing_display_names,
1061-
task_description=import_agent_info.business_description or import_agent_info.description or "",
1062-
model_id=regeneration_model_id,
1063-
tenant_id=tenant_id,
1064-
language=LANGUAGE["ZH"], # Default to Chinese, can be enhanced later
1065-
agents_cache=all_agents,
1066-
)
1067-
logger.info(f"Regenerated agent display_name: '{agent_display_name}'")
1068-
except Exception as e:
1069-
logger.error(f"Failed to regenerate agent display_name with LLM: {str(e)}, using fallback")
1070-
agent_display_name = _generate_unique_display_name_with_suffix(
1071-
agent_display_name,
1072-
tenant_id=tenant_id,
1073-
agents_cache=all_agents
1074-
)
1075-
else:
1076-
logger.warning("No model available for regeneration, using fallback")
1077-
agent_display_name = _generate_unique_display_name_with_suffix(
1078-
agent_display_name,
1079-
tenant_id=tenant_id,
1080-
agents_cache=all_agents
1081-
)
1082-
10831154
# create a new agent
10841155
new_agent = create_agent(agent_info={"name": agent_name,
10851156
"display_name": agent_display_name,

0 commit comments

Comments
 (0)