Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions backend/consts/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,12 @@ class AgentSkillInstanceRequest(BaseModel):


# used in agent/search agent/update for save agent info
class RelatedAgentInfo(BaseModel):
"""Related agent info with pinned version."""
agent_id: int
version_no: Optional[int] = None


class AgentInfoRequest(BaseModel):
agent_id: Optional[int] = None
name: Optional[str] = None
Expand All @@ -611,6 +617,7 @@ class AgentInfoRequest(BaseModel):
enabled_skill_ids: Optional[List[int]] = None
skill_instances: Optional[List[AgentSkillInstanceRequest]] = None
related_agent_ids: Optional[List[int]] = None
related_agents: Optional[List[RelatedAgentInfo]] = None # Related agents with pinned versions
related_external_agent_ids: Optional[List[int]] = None
group_ids: Optional[List[int]] = None
ingroup_permission: Optional[str] = None
Expand Down
119 changes: 94 additions & 25 deletions backend/database/agent_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,32 @@ def query_all_agent_info_by_tenant_id(tenant_id: str, version_no: int = 0):
return [as_dict(agent) for agent in agents]


def insert_related_agent(parent_agent_id: int, child_agent_id: int, tenant_id: str, user_id: str, version_no: int = 0) -> bool:
def batch_search_agent_display_names(agent_ids: List[int], tenant_id: str) -> dict:
"""
Batch query agent display names by agent IDs.
Returns a dict mapping agent_id -> display_name (falls back to name).

Args:
agent_ids: List of agent IDs to query
tenant_id: Tenant ID
"""
if not agent_ids:
return {}
with get_db_session() as session:
agents = session.query(
AgentInfo.agent_id,
AgentInfo.display_name,
AgentInfo.name
).filter(
AgentInfo.agent_id.in_(agent_ids),
AgentInfo.tenant_id == tenant_id,
AgentInfo.version_no == 0,
AgentInfo.delete_flag != 'Y'
).all()
return {a.agent_id: (a.display_name or a.name) for a in agents}


def insert_related_agent(parent_agent_id: int, child_agent_id: int, tenant_id: str, user_id: str, version_no: int = 0, selected_agent_version_no: Optional[int] = None) -> bool:
"""
Insert a related agent.
Default version_no=0 creates the draft version.
Expand All @@ -349,14 +374,16 @@ def insert_related_agent(parent_agent_id: int, child_agent_id: int, tenant_id: s
child_agent_id: Child agent ID
tenant_id: Tenant ID
user_id: User ID
version_no: Version number. Default 0 = draft/editing state
version_no: Parent agent version number. Default 0 = draft/editing state
selected_agent_version_no: Pinned version of child agent. None = runtime fallback to child current_version_no
"""
try:
relation_info = {
"parent_agent_id": parent_agent_id,
"selected_agent_id": child_agent_id,
"tenant_id": tenant_id,
"version_no": version_no,
"selected_agent_version_no": selected_agent_version_no,
"created_by": user_id,
"updated_by": user_id
}
Expand Down Expand Up @@ -398,40 +425,90 @@ def delete_related_agent(parent_agent_id: int, child_agent_id: int, tenant_id: s
return False


def update_related_agents(parent_agent_id: int, related_agent_ids: List[int], tenant_id: str, user_id: str, version_no: int = 0):
def _parse_related_agents(related_agents: Optional[List[dict]]) -> tuple:
"""Extract agent_id set and version_map from related_agents list."""
new_related_ids: set = set()
version_map: dict = {}
if not related_agents:
return new_related_ids, version_map
for rel in related_agents:
agent_id = rel.get("agent_id")
if agent_id is None:
continue
new_related_ids.add(agent_id)
version_no_val = rel.get("version_no")
if version_no_val is not None:
version_map[agent_id] = version_no_val
return new_related_ids, version_map


def _add_new_relations(session, parent_agent_id, tenant_id, user_id, version_no, ids_to_add, version_map):
"""Insert new agent relations into the database."""
for child_agent_id in ids_to_add:
relation_info = {
"parent_agent_id": parent_agent_id,
"selected_agent_id": child_agent_id,
"tenant_id": tenant_id,
"version_no": version_no,
"created_by": user_id,
"updated_by": user_id,
}
if child_agent_id in version_map:
relation_info["selected_agent_version_no"] = version_map[child_agent_id]
new_relation = AgentRelation(**filter_property(relation_info, AgentRelation))
session.add(new_relation)


def _update_existing_relations(current_relations, ids_to_update, version_map, user_id):
"""Update version_no for existing relations."""
if not ids_to_update or not version_map:
return
for rel in current_relations:
if rel.selected_agent_id not in ids_to_update:
continue
new_version_no = version_map.get(rel.selected_agent_id)
if new_version_no is not None:
rel.selected_agent_version_no = new_version_no
rel.updated_by = user_id


def update_related_agents(
parent_agent_id: int,
tenant_id: str,
user_id: str,
related_agents: Optional[List[dict]] = None,
version_no: int = 0,
):
"""
Update related agents for a parent agent by replacing all existing relations.
Default version_no=0 updates the draft version.

This function handles both creation and deletion of relations in a single transaction.
related_agents is the single source of truth: each item has 'agent_id' and optional 'version_no'.

Args:
parent_agent_id: ID of the parent agent
related_agent_ids: List of child agent IDs to be related
tenant_id: Tenant ID
user_id: User ID for audit trail
related_agents: List of dicts with 'agent_id' and optional 'version_no' keys
version_no: Version number to filter. Default 0 = draft/editing state
"""
new_related_ids, version_map = _parse_related_agents(related_agents)

with get_db_session() as session:
# Get current relations
current_relations = session.query(AgentRelation).filter(
AgentRelation.parent_agent_id == parent_agent_id,
AgentRelation.tenant_id == tenant_id,
AgentRelation.version_no == version_no,
AgentRelation.delete_flag != 'Y'
).all()

current_related_ids = {
rel.selected_agent_id for rel in current_relations}
new_related_ids = set(
related_agent_ids) if related_agent_ids else set()
current_related_ids = {rel.selected_agent_id for rel in current_relations}

# Find IDs to delete (in current but not in new)
ids_to_delete = current_related_ids - new_related_ids
# Find IDs to add (in new but not in current)
ids_to_add = new_related_ids - current_related_ids
ids_to_update = current_related_ids & new_related_ids

# Soft delete removed relations
if ids_to_delete:
session.query(AgentRelation).filter(
AgentRelation.parent_agent_id == parent_agent_id,
Expand All @@ -443,19 +520,11 @@ def update_related_agents(parent_agent_id: int, related_agent_ids: List[int], te
synchronize_session=False
)

# Add new relations
for child_agent_id in ids_to_add:
relation_info = {
"parent_agent_id": parent_agent_id,
"selected_agent_id": child_agent_id,
"tenant_id": tenant_id,
"version_no": version_no,
"created_by": user_id,
"updated_by": user_id
}
new_relation = AgentRelation(
**filter_property(relation_info, AgentRelation))
session.add(new_relation)
_add_new_relations(
session, parent_agent_id, tenant_id, user_id, version_no, ids_to_add, version_map
)

_update_existing_relations(current_relations, ids_to_update, version_map, user_id)


def delete_agent_relationship(agent_id: int, tenant_id: str, user_id: str, version_no: int = 0):
Expand Down
66 changes: 66 additions & 0 deletions backend/database/agent_version_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,36 @@ def search_version_by_version_no(
return as_dict(version) if version else None


def batch_search_version_names(
agent_ids: List[int],
tenant_id: str,
version_nos: List[int],
) -> List[dict]:
"""
Batch query version names for multiple (agent_id, version_no) pairs.

Returns list of dicts: [{"agent_id": int, "version_no": int, "version_name": Optional[str]}]
"""
if not agent_ids or not version_nos:
return []

with get_db_session() as session:
versions = session.query(AgentVersion).filter(
AgentVersion.agent_id.in_(agent_ids),
AgentVersion.version_no.in_(version_nos),
AgentVersion.tenant_id == tenant_id,
).all()

result = []
for v in versions:
result.append({
"agent_id": v.agent_id,
"version_no": v.version_no,
"version_name": v.version_name,
})
return result


def search_version_by_id(
version_id: int,
tenant_id: str,
Expand Down Expand Up @@ -87,6 +117,42 @@ def query_current_version_no(
return agent.current_version_no if agent else None


def batch_query_current_version_nos(
agent_ids: List[int],
tenant_id: str,
) -> dict:
"""
Batch query current published version_no for multiple agents.

Returns a dict mapping agent_id -> current_version_no (only includes agents
that have a non-null current_version_no).

Args:
agent_ids: List of agent IDs to query
tenant_id: Tenant ID
"""
if not agent_ids:
return {}
with get_db_session() as session:
agents = session.query(
AgentInfo.agent_id,
AgentInfo.current_version_no,
).filter(
AgentInfo.agent_id.in_(agent_ids),
or_(
AgentInfo.tenant_id == tenant_id,
AgentInfo.tenant_id == ASSET_OWNER_TENANT_ID,
),
AgentInfo.version_no == 0,
AgentInfo.delete_flag == 'N',
).all()
return {
a.agent_id: a.current_version_no
for a in agents
if a.current_version_no is not None
}


def query_agent_snapshot(
agent_id: int,
tenant_id: str,
Expand Down
7 changes: 4 additions & 3 deletions backend/database/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1074,18 +1074,19 @@ class UserTenant(TableBase):
class AgentRelation(TableBase):
"""
Agent parent-child relationship table
Primary key: (relation_id, version_no)
"""
__tablename__ = "ag_agent_relation_t"
__table_args__ = {"schema": SCHEMA}

relation_id = Column(Integer, Sequence("ag_agent_relation_t_relation_id_seq", schema=SCHEMA),
primary_key=True, nullable=False, doc="Relationship ID, primary key")
version_no = Column(Integer, primary_key=True, default=0, nullable=False,
doc="Version number. 0 = draft/editing state, >=1 = published snapshot")
selected_agent_id = Column(
Integer, primary_key=True, doc="Selected agent ID")
Integer, doc="Selected agent ID")
parent_agent_id = Column(Integer, doc="Parent agent ID")
tenant_id = Column(String(100), doc="Tenant ID")
version_no = Column(Integer, default=0, nullable=False,
doc="Version number. 0 = draft/editing state, >=1 = published snapshot")
selected_agent_version_no = Column(
Integer, nullable=True,
doc="Pinned version of selected_agent_id. NULL = runtime fallback to child current_version_no",
Expand Down
Loading
Loading