Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b6be9f4
Feat: Add A2A protocol support for agent publishing and external agen…
xuyaqist Apr 13, 2026
0f25486
Potential fix for pull request finding 'CodeQL / Information exposure…
xuyaqist Apr 13, 2026
c551a15
Potential fix for pull request finding 'CodeQL / Information exposure…
xuyaqist Apr 13, 2026
9a42e96
Bugfix: temporarily not support grpc for external 2a2 agent
xuyaqist Apr 13, 2026
587a714
Bugfix: 解决在 AgentConfig 类中(第36-52行)引用了 ExternalA2AAgentConfig,但后者在第10…
xuyaqist Apr 13, 2026
a3c33f5
delete debug logger
xuyaqist Apr 13, 2026
b79298d
delete unsafe log
xuyaqist Apr 13, 2026
fcd50bc
Bugfix: 使用 A2A 1.0 封套格式检测流式终止状态
xuyaqist Apr 13, 2026
0b554cf
Bugfix: 删除对a2a 0.3版本对kind字段的兼容
xuyaqist Apr 13, 2026
88ecf93
Bugfix: 修复了只读模式下关联代理信息完全隐藏的问题
xuyaqist Apr 13, 2026
1809e90
Bugfix: 当 supported_interfaces 同时包含两种协议接口且 jsonrpcInterface 有自定义 url …
xuyaqist Apr 13, 2026
43f8121
Bugfix: 当触发 aiohttp.ClientError 或通用 Exception 时,代码会抛出 NameError: name…
xuyaqist Apr 13, 2026
91d9095
Bugfix: 确保了 HTTP 连接的正确释放,避免在高负载场景下出现连接泄漏问题
xuyaqist Apr 13, 2026
6a42e99
Bugfix: 修复数据库允许 task_id 为 NULL,但 ORM 层强制要求非空的问题
xuyaqist Apr 13, 2026
eaee161
update sql, make sure sql is consistent with orm modal
xuyaqist Apr 13, 2026
02d45e0
delete unused code
xuyaqist Apr 13, 2026
8d06138
把详情日志降级为 debug 级别,只在 INFO 保留计数
xuyaqist Apr 13, 2026
d38ff5e
Refactor this function to reduce its Cognitive Complexity from 30 to …
xuyaqist Apr 13, 2026
f13e356
Use "Annotated" type hints for FastAPI dependency injection
xuyaqist Apr 13, 2026
118e2d5
fix code analysis
xuyaqist Apr 13, 2026
e8eac73
Replace the unused local variable "user_id" with "_"
xuyaqist Apr 13, 2026
93ce3bd
Bugfix: 修复代码圈复杂度&定义常量避免重复
xuyaqist Apr 14, 2026
9ee3073
修复圈复杂度
xuyaqist Apr 14, 2026
51ac2cf
修复sonarlcoud问题
xuyaqist Apr 14, 2026
79841b9
修复报错
xuyaqist Apr 14, 2026
d5b3498
修复循环依赖
xuyaqist Apr 14, 2026
ef11b30
修复单元测试问题
xuyaqist Apr 14, 2026
ffeaf38
修复单元测试
xuyaqist Apr 14, 2026
0604d55
新增northbound单元测试
xuyaqist Apr 14, 2026
6fbff26
修复代码报错
xuyaqist Apr 14, 2026
8f25a88
优化前端展示数据样式
xuyaqist Apr 15, 2026
7c12f68
修改报错
xuyaqist Apr 15, 2026
730d2c7
新增单元测试
xuyaqist Apr 15, 2026
dd1e495
新增测试用例
xuyaqist Apr 15, 2026
212c3d5
新增单元测试
xuyaqist Apr 15, 2026
034a04f
新增单元测试
xuyaqist Apr 16, 2026
fa1b07c
新增单元测试
xuyaqist Apr 16, 2026
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
114 changes: 105 additions & 9 deletions backend/agents/create_agent_info.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import threading
import logging
from typing import List
from typing import List, Optional
from urllib.parse import urljoin
from datetime import datetime

from jinja2 import Template, StrictUndefined
from nexent.core.utils.observer import MessageObserver
from nexent.core.agents.agent_model import AgentRunInfo, ModelConfig, AgentConfig, ToolConfig
from nexent.core.agents.agent_model import AgentRunInfo, ModelConfig, AgentConfig, ToolConfig, ExternalA2AAgentConfig
from nexent.memory.memory_service import search_memory_in_levels

from services.file_management_service import get_llm_model
Expand All @@ -17,6 +17,8 @@
get_rerank_model,
)
from services.remote_mcp_service import get_remote_mcp_server_list

from database.a2a_agent_db import PROTOCOL_JSONRPC
from services.memory_config_service import build_memory_context
from services.image_service import get_vlm_model
from database.agent_db import search_agent_info_by_agent_id, query_sub_agents_id_list
Expand Down Expand Up @@ -66,6 +68,95 @@ def _get_skills_for_template(
return []


def _extract_url_from_card(raw_card: Optional[dict]) -> str:
"""Extract http-json-rpc URL from Agent Card supportedInterfaces."""
if not raw_card:
return ""

supported_interfaces = raw_card.get("supportedInterfaces", [])
if not supported_interfaces:
return raw_card.get("url", "")

# Prefer http-json-rpc protocol
for iface in supported_interfaces:
protocol_binding = iface.get("protocolBinding", "").lower()
if protocol_binding in ("http-json-rpc", "jsonrpc", "httpjsonrpc"):
url = iface.get("url", "")
if url:
return url

# Fallback to first interface with a URL
for iface in supported_interfaces:
url = iface.get("url", "")
if url:
return url

return raw_card.get("url", "")


def _build_external_agent_config(agent: dict, agent_url: str) -> ExternalA2AAgentConfig:
"""Build an ExternalA2AAgentConfig from agent data."""
return ExternalA2AAgentConfig(
agent_id=str(agent.get("external_agent_id", "")),
name=agent.get("name", "Unknown"),
description=agent.get("description", "External A2A agent"),
url=agent_url,
api_key=None,
transport_type=agent.get("transport_type", "http-streaming"),
protocol_version=agent.get("protocol_version", "1.0"),
protocol_type=agent.get("protocol_type", PROTOCOL_JSONRPC),
timeout=300.0,
raw_card=agent.get("raw_card"),
)


def _get_external_a2a_agents(
agent_id: int,
tenant_id: str,
version_no: int = 0
) -> List[ExternalA2AAgentConfig]:
"""Get external A2A agent configurations for an agent.

Args:
agent_id: Agent ID
tenant_id: Tenant ID
version_no: Version number

Returns:
List of ExternalA2AAgentConfig for external A2A sub-agents
"""
logger.info(f"[_get_external_a2a_agents] START - agent_id={agent_id}, tenant_id={tenant_id}")
try:
from database import a2a_agent_db

external_agents = a2a_agent_db.query_external_sub_agents(
local_agent_id=agent_id,
tenant_id=tenant_id,
version_no=version_no,
)
logger.info(f"[_get_external_a2a_agents] DB query returned {len(external_agents)} agents")
logger.debug(f"[_get_external_a2a_agents] agent details: {external_agents}")

result = []
for agent in external_agents:
agent_url = agent.get("agent_url", "") or _extract_url_from_card(agent.get("raw_card"))
if not agent_url:
logger.warning(
f"[_get_external_a2a_agents] Skipping agent '{agent.get('name')}' - no URL available"
)
continue

result.append(_build_external_agent_config(agent, agent_url))

logger.info(f"[_get_external_a2a_agents] returning {len(result)} ExternalA2AAgentConfig")
for i, config in enumerate(result):
logger.info(f" [{i}] name={config.name}, description={config.description}")
return result
except Exception as e:
logger.error(f"[_get_external_a2a_agents] FAILED: {e}", exc_info=True)
return []


def _get_skill_script_tools(
agent_id: int,
tenant_id: str,
Expand Down Expand Up @@ -209,16 +300,19 @@ async def create_agent_config(
)
managed_agents.append(sub_agent_config)

# create external A2A agents (synchronous function, no await needed)
external_a2a_agents = _get_external_a2a_agents(agent_id, tenant_id, version_no)

tool_list = await create_tool_config_list(agent_id, tenant_id, user_id, version_no=version_no)

# Build system prompt: prioritize segmented fields, fallback to original prompt field if not available
duty_prompt = agent_info.get("duty_prompt", "")
constraint_prompt = agent_info.get("constraint_prompt", "")
few_shots_prompt = agent_info.get("few_shots_prompt", "")

# Get template content
prompt_template = get_agent_prompt_template(
is_manager=len(managed_agents) > 0, language=language)
# Get template content (use manager template if has any sub-agents)
is_manager = len(managed_agents) > 0 or len(external_a2a_agents) > 0
prompt_template = get_agent_prompt_template(is_manager=is_manager, language=language)

# Get app information
default_app_description = 'Nexent 是一个开源智能体SDK和平台' if language == 'zh' else 'Nexent is an open-source agent SDK and platform'
Expand All @@ -228,7 +322,7 @@ async def create_agent_config(
'APP_DESCRIPTION', tenant_id=tenant_id) or default_app_description

# Get memory list
memory_context = build_memory_context(user_id, tenant_id, agent_id)
memory_context = build_memory_context(user_id, tenant_id, agent_id, skip_query=not allow_memory_search)
memory_list = []
if allow_memory_search and memory_context.user_config.memory_switch:
logger.debug("Retrieving memory list...")
Expand Down Expand Up @@ -288,6 +382,7 @@ async def create_agent_config(
"tools": {tool.name: tool for tool in tool_list},
"skills": skills,
"managed_agents": {agent.name: agent for agent in managed_agents},
"external_a2a_agents": {agent.agent_id: agent for agent in external_a2a_agents},
"APP_NAME": app_name,
"APP_DESCRIPTION": app_description,
"memory_list": memory_list,
Expand All @@ -306,7 +401,7 @@ async def create_agent_config(
name="undefined" if agent_info["name"] is None else agent_info["name"],
description="undefined" if agent_info["description"] is None else agent_info["description"],
prompt_templates=await prepare_prompt_templates(
is_manager=len(managed_agents) > 0,
is_manager=len(managed_agents) > 0 or len(external_a2a_agents) > 0,
system_prompt=system_prompt,
language=language,
agent_id=agent_id
Expand All @@ -315,7 +410,8 @@ async def create_agent_config(
max_steps=agent_info.get("max_steps", 10),
model_name=model_name,
provide_run_summary=agent_info.get("provide_run_summary", False),
managed_agents=managed_agents
managed_agents=managed_agents,
external_a2a_agents=external_a2a_agents
)
return agent_config

Expand Down Expand Up @@ -482,7 +578,7 @@ def check_agent_tools(agent_config: AgentConfig):
used_mcp_urls.add(
mcp_info_dict[tool.usage]["remote_mcp_server"])

# Recursively check sub-agent
# Recursively check sub-agents (only internal AgentConfig, not external A2A)
for sub_agent_config in agent_config.managed_agents:
check_agent_tools(sub_agent_config)

Expand Down
Loading
Loading