Skip to content

Commit 63478aa

Browse files
authored
✨ Now user can save or discard changes in agent config page
2 parents a42c614 + 576410e commit 63478aa

File tree

22 files changed

+2023
-747
lines changed

22 files changed

+2023
-747
lines changed

backend/apps/agent_app.py

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from typing import Optional
44

55
from fastapi import APIRouter, Body, Header, HTTPException, Request
6-
from fastapi.responses import JSONResponse
76

87
from consts.model import AgentRequest, AgentInfoRequest, AgentIDRequest, ConversationResponse, AgentImportRequest
98
from services.agent_service import (
@@ -14,11 +13,9 @@
1413
export_agent_impl,
1514
import_agent_impl,
1615
list_all_agent_info_impl,
17-
insert_related_agent_impl,
1816
run_agent_stream,
1917
stop_agent_tasks,
20-
get_agent_call_relationship_impl,
21-
delete_related_agent_impl
18+
get_agent_call_relationship_impl
2219
)
2320
from utils.auth_utils import get_current_user_info, get_current_user_id
2421

@@ -94,8 +91,8 @@ async def update_agent_info_api(request: AgentInfoRequest, authorization: Option
9491
Update an existing agent
9592
"""
9693
try:
97-
await update_agent_info_impl(request, authorization)
98-
return {}
94+
result = await update_agent_info_impl(request, authorization)
95+
return result or {}
9996
except Exception as e:
10097
logger.error(f"Agent update error: {str(e)}")
10198
raise HTTPException(
@@ -158,42 +155,6 @@ async def list_all_agent_info_api(authorization: Optional[str] = Header(None), r
158155
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent list error.")
159156

160157

161-
@router.post("/related_agent")
162-
async def related_agent_api(parent_agent_id: int = Body(...),
163-
child_agent_id: int = Body(...),
164-
authorization: Optional[str] = Header(None)):
165-
"""
166-
get related agent info
167-
"""
168-
try:
169-
_, tenant_id = get_current_user_id(authorization)
170-
return insert_related_agent_impl(parent_agent_id=parent_agent_id,
171-
child_agent_id=child_agent_id,
172-
tenant_id=tenant_id)
173-
except Exception as e:
174-
logger.error(f"Agent related info error: {str(e)}")
175-
return JSONResponse(
176-
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
177-
content={"message": "Failed to insert relation", "status": "error"}
178-
)
179-
180-
181-
@router.post("/delete_related_agent")
182-
async def delete_related_agent_api(parent_agent_id: int = Body(...),
183-
child_agent_id: int = Body(...),
184-
authorization: Optional[str] = Header(None)):
185-
"""
186-
delete related agent info
187-
"""
188-
try:
189-
_, tenant_id = get_current_user_id(authorization)
190-
return delete_related_agent_impl(parent_agent_id, child_agent_id, tenant_id)
191-
except Exception as e:
192-
logger.error(f"Agent related info error: {str(e)}")
193-
raise HTTPException(
194-
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="Agent related info error.")
195-
196-
197158
@router.get("/call_relationship/{agent_id}")
198159
async def get_agent_call_relationship_api(agent_id: int, authorization: Optional[str] = Header(None)):
199160
"""

backend/consts/model.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ class GenerateTitleRequest(BaseModel):
202202

203203
# used in agent/search agent/update for save agent info
204204
class AgentInfoRequest(BaseModel):
205-
agent_id: int
205+
agent_id: Optional[int] = None
206206
name: Optional[str] = None
207207
display_name: Optional[str] = None
208208
description: Optional[str] = None
@@ -217,6 +217,8 @@ class AgentInfoRequest(BaseModel):
217217
enabled: Optional[bool] = None
218218
business_logic_model_name: Optional[str] = None
219219
business_logic_model_id: Optional[int] = None
220+
enabled_tool_ids: Optional[List[int]] = None
221+
related_agent_ids: Optional[List[int]] = None
220222

221223

222224
class AgentIDRequest(BaseModel):

backend/database/agent_db.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from typing import List
23

34
from database.client import get_db_session, as_dict, filter_property
45
from database.db_models import AgentInfo, ToolInstance, AgentRelation
@@ -169,13 +170,68 @@ def delete_related_agent(parent_agent_id: int, child_agent_id: int, tenant_id: s
169170
session.query(AgentRelation).filter(AgentRelation.parent_agent_id == parent_agent_id,
170171
AgentRelation.selected_agent_id == child_agent_id,
171172
AgentRelation.tenant_id == tenant_id).update(
172-
{ToolInstance.delete_flag: 'Y', 'updated_by': tenant_id})
173+
{AgentRelation.delete_flag: 'Y', 'updated_by': tenant_id})
173174
return True
174175
except Exception as e:
175176
logger.error(f"Failed to delete related agent: {str(e)}")
176177
return False
177178

178179

180+
def update_related_agents(parent_agent_id: int, related_agent_ids: List[int], tenant_id: str, user_id: str):
181+
"""
182+
Update related agents for a parent agent by replacing all existing relations.
183+
This function handles both creation and deletion of relations in a single transaction.
184+
:param parent_agent_id: ID of the parent agent
185+
:param related_agent_ids: List of child agent IDs to be related
186+
:param tenant_id: Tenant ID
187+
:param user_id: User ID for audit trail
188+
:return: None
189+
"""
190+
with get_db_session() as session:
191+
# Get current relations
192+
current_relations = session.query(AgentRelation).filter(
193+
AgentRelation.parent_agent_id == parent_agent_id,
194+
AgentRelation.tenant_id == tenant_id,
195+
AgentRelation.delete_flag != 'Y'
196+
).all()
197+
198+
current_related_ids = {
199+
rel.selected_agent_id for rel in current_relations}
200+
new_related_ids = set(
201+
related_agent_ids) if related_agent_ids else set()
202+
203+
# Find IDs to delete (in current but not in new)
204+
ids_to_delete = current_related_ids - new_related_ids
205+
# Find IDs to add (in new but not in current)
206+
ids_to_add = new_related_ids - current_related_ids
207+
208+
# Soft delete removed relations
209+
if ids_to_delete:
210+
session.query(AgentRelation).filter(
211+
AgentRelation.parent_agent_id == parent_agent_id,
212+
AgentRelation.selected_agent_id.in_(ids_to_delete),
213+
AgentRelation.tenant_id == tenant_id
214+
).update(
215+
{AgentRelation.delete_flag: 'Y', 'updated_by': user_id},
216+
synchronize_session=False
217+
)
218+
219+
# Add new relations
220+
for child_agent_id in ids_to_add:
221+
relation_info = {
222+
"parent_agent_id": parent_agent_id,
223+
"selected_agent_id": child_agent_id,
224+
"tenant_id": tenant_id,
225+
"created_by": user_id,
226+
"updated_by": user_id
227+
}
228+
new_relation = AgentRelation(
229+
**filter_property(relation_info, AgentRelation))
230+
session.add(new_relation)
231+
232+
session.commit()
233+
234+
179235
def delete_agent_relationship(agent_id: int, tenant_id: str, user_id: str):
180236
with get_db_session() as session:
181237
session.query(AgentRelation).filter(AgentRelation.parent_agent_id == agent_id,

backend/services/agent_service.py

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

89
from fastapi import Header, Request
910
from fastapi.responses import JSONResponse, StreamingResponse
@@ -35,7 +36,8 @@
3536
search_agent_id_by_agent_name,
3637
search_agent_info_by_agent_id,
3738
search_blank_sub_agent_by_main_agent_id,
38-
update_agent
39+
update_agent,
40+
update_related_agents
3941
)
4042
from database.model_management_db import get_model_by_model_id, get_model_id_by_display_name
4143
from database.remote_mcp_db import check_mcp_name_exists, get_mcp_server_by_name_and_tenant
@@ -45,6 +47,7 @@
4547
delete_tools_by_agent_id,
4648
query_all_enabled_tool_instances,
4749
query_all_tools,
50+
query_tool_instances_by_id,
4851
search_tools_for_sub_agent
4952
)
5053
from services.conversation_management_service import save_conversation_assistant, save_conversation_user
@@ -340,12 +343,100 @@ async def get_creating_sub_agent_info_impl(authorization: str = Header(None)):
340343
async def update_agent_info_impl(request: AgentInfoRequest, authorization: str = Header(None)):
341344
user_id, tenant_id, _ = get_current_user_info(authorization)
342345

346+
# If agent_id is None, create a new agent; otherwise, update existing
347+
agent_id: Optional[int] = request.agent_id
343348
try:
344-
update_agent(request.agent_id, request, tenant_id, user_id)
349+
if agent_id is None:
350+
# Create agent
351+
created = create_agent(agent_info={
352+
"name": request.name,
353+
"display_name": request.display_name,
354+
"description": request.description,
355+
"business_description": request.business_description,
356+
"model_id": request.model_id,
357+
"model_name": request.model_name,
358+
"business_logic_model_id": request.business_logic_model_id,
359+
"business_logic_model_name": request.business_logic_model_name,
360+
"max_steps": request.max_steps,
361+
"provide_run_summary": request.provide_run_summary,
362+
"duty_prompt": request.duty_prompt,
363+
"constraint_prompt": request.constraint_prompt,
364+
"few_shots_prompt": request.few_shots_prompt,
365+
"enabled": request.enabled if request.enabled is not None else True
366+
}, tenant_id=tenant_id, user_id=user_id)
367+
agent_id = created["agent_id"]
368+
else:
369+
# Update agent
370+
update_agent(agent_id, request, tenant_id, user_id)
345371
except Exception as e:
346372
logger.error(f"Failed to update agent info: {str(e)}")
347373
raise ValueError(f"Failed to update agent info: {str(e)}")
348374

375+
# Handle enabled tools saving when provided
376+
try:
377+
if request.enabled_tool_ids is not None and agent_id is not None:
378+
enabled_set = set(request.enabled_tool_ids)
379+
# Get all tools for current tenant
380+
all_tools = query_all_tools(tenant_id=tenant_id)
381+
for tool in all_tools:
382+
tool_id = tool.get("tool_id")
383+
if tool_id is None:
384+
continue
385+
# Keep existing params if any
386+
existing_instance = query_tool_instances_by_id(
387+
agent_id, tool_id, tenant_id)
388+
params = (existing_instance or {}).get(
389+
"params", {}) if existing_instance else {}
390+
create_or_update_tool_by_tool_info(
391+
tool_info=ToolInstanceInfoRequest(
392+
tool_id=tool_id,
393+
agent_id=agent_id,
394+
params=params,
395+
enabled=(tool_id in enabled_set)
396+
),
397+
tenant_id=tenant_id,
398+
user_id=user_id
399+
)
400+
except Exception as e:
401+
logger.error(f"Failed to update agent tools: {str(e)}")
402+
raise ValueError(f"Failed to update agent tools: {str(e)}")
403+
404+
# Handle related agents saving when provided
405+
try:
406+
if request.related_agent_ids is not None and agent_id is not None:
407+
related_agent_ids = request.related_agent_ids
408+
# Check for circular dependencies using BFS
409+
search_list = deque(related_agent_ids)
410+
agent_id_set = set()
411+
412+
while len(search_list):
413+
left_ele = search_list.popleft()
414+
if left_ele == agent_id:
415+
raise ValueError("Circular dependency detected: Agent cannot be related to itself or create circular calls")
416+
if left_ele in agent_id_set:
417+
continue
418+
else:
419+
agent_id_set.add(left_ele)
420+
sub_ids = query_sub_agents_id_list(
421+
main_agent_id=left_ele, tenant_id=tenant_id)
422+
search_list.extend(sub_ids)
423+
424+
# Update related agents
425+
update_related_agents(
426+
parent_agent_id=agent_id,
427+
related_agent_ids=related_agent_ids,
428+
tenant_id=tenant_id,
429+
user_id=user_id
430+
)
431+
except ValueError as e:
432+
# Re-raise ValueError (circular dependency) as-is
433+
raise
434+
except Exception as e:
435+
logger.error(f"Failed to update related agents: {str(e)}")
436+
raise ValueError(f"Failed to update related agents: {str(e)}")
437+
438+
return {"agent_id": agent_id}
439+
349440

350441
async def delete_agent_impl(agent_id: int, authorization: str = Header(None)):
351442
user_id, tenant_id, _ = get_current_user_info(authorization)

0 commit comments

Comments
 (0)