Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
4 changes: 1 addition & 3 deletions backend/agents/create_agent_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,8 @@ async def create_agent_config(
model_max_tokens = model_info["max_tokens"]
else:
model_name = "main_model"
# Use agent-level setting for context management, default to False
enable_context_manager = agent_info.get("enable_context_manager", False)
cm_config = ContextManagerConfig(
enabled=enable_context_manager,
enabled=False,
token_threshold=model_max_tokens,
)
agent_config = AgentConfig(
Expand Down
6 changes: 6 additions & 0 deletions backend/apps/a2a_client_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ async def list_external_agents(
)

except Exception as e:
# Return empty list if table doesn't exist
if "does not exist" in str(e).lower():
return JSONResponse(
status_code=HTTPStatus.OK,
content={"status": "success", "data": []}
)
logger.error(f"List agents failed: {e}", exc_info=True)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
Expand Down
3 changes: 3 additions & 0 deletions backend/apps/skill_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ async def list_skills() -> JSONResponse:
except SkillException as e:
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
# Return empty list if table doesn't exist
if "does not exist" in str(e).lower():
return JSONResponse(content={"skills": []})
logger.error(f"Error listing skills: {e}")
raise HTTPException(status_code=500, detail="Internal server error")

Expand Down
5 changes: 4 additions & 1 deletion backend/consts/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,10 @@ class VectorDatabaseType(str, Enum):
"rerank": "RERANK_ID",
"vlm": "VLM_ID",
"stt": "STT_ID",
"tts": "TTS_ID"
"tts": "TTS_ID",
"imageUnderstanding": "IMAGE_UNDERSTANDING_ID",
"imageGeneration": "IMAGE_GENERATION_ID",
"videoUnderstanding": "VIDEO_UNDERSTANDING_ID"
}

APP_NAME = "APP_NAME"
Expand Down
7 changes: 5 additions & 2 deletions backend/consts/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,11 @@ class ModelConfig(BaseModel):
multiEmbedding: SingleModelConfig
rerank: SingleModelConfig
vlm: SingleModelConfig
stt: STTModelConfig
stt: SingleModelConfig
tts: SingleModelConfig
imageUnderstanding: SingleModelConfig
imageGeneration: SingleModelConfig
videoUnderstanding: SingleModelConfig


class AppConfig(BaseModel):
Expand Down Expand Up @@ -349,7 +353,6 @@ class AgentInfoRequest(BaseModel):
related_external_agent_ids: Optional[List[int]] = None
group_ids: Optional[List[int]] = None
ingroup_permission: Optional[str] = None
enable_context_manager: Optional[bool] = None
version_no: int = 0


Expand Down
5 changes: 5 additions & 0 deletions backend/consts/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ class ProviderEnum(str, Enum):
SILICON_BASE_URL = "https://api.siliconflow.cn/v1/"
SILICON_GET_URL = "https://api.siliconflow.cn/v1/models"

# Silicon Flow model tags (for filtering)
# Based on SiliconFlow website: https://cloud.siliconflow.cn/me/models
SILICON_TAG_VISION = "VLM" # Vision tag - VLM models (e.g., Kimi-K2.6, Qwen3.6)
SILICON_TAG_VIDEO = "视频" # Video tag - Video understanding models

# Dashcope
DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1/"
DASHSCOPE_GET_URL = "https://dashscope.aliyuncs.com/api/v1/models"
Expand Down
19 changes: 13 additions & 6 deletions backend/database/a2a_agent_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -1245,12 +1245,19 @@ def get_server_agent_ids(tenant_id: str) -> set[int]:
Returns:
Set of agent IDs that have A2A Server registration.
"""
with _get_db_session() as session:
agent_ids = session.query(A2AServerAgent.agent_id).filter(
A2AServerAgent.tenant_id == tenant_id,
A2AServerAgent.delete_flag != 'Y'
).all()
return {row[0] for row in agent_ids}
try:
with _get_db_session() as session:
agent_ids = session.query(A2AServerAgent.agent_id).filter(
A2AServerAgent.tenant_id == tenant_id,
A2AServerAgent.delete_flag != 'Y'
).all()
return {row[0] for row in agent_ids}
except Exception as e:
# Return empty set if table doesn't exist (migration not applied)
if "does not exist" in str(e).lower():
logger.warning(f"A2A server agent table not found, returning empty set: {e}")
return set()
raise


# =============================================================================
Expand Down
1 change: 0 additions & 1 deletion backend/database/agent_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ def create_agent(agent_info, tenant_id: str, user_id: str):
"business_logic_model_name": new_agent.business_logic_model_name,
"group_ids": new_agent.group_ids,
"is_new": new_agent.is_new,
"enable_context_manager": new_agent.enable_context_manager,
"current_version_no": new_agent.current_version_no,
"version_no": new_agent.version_no,
"created_by": new_agent.created_by,
Expand Down
78 changes: 65 additions & 13 deletions backend/database/agent_version_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,42 @@
"""
Query version list for an agent
"""
with get_db_session() as session:
versions = session.query(AgentVersion).filter(
AgentVersion.agent_id == agent_id,
AgentVersion.tenant_id == tenant_id,
AgentVersion.delete_flag == 'N',
).order_by(AgentVersion.version_no.desc()).all()

return [as_dict(v) for v in versions]
try:
with get_db_session() as session:
versions = session.query(AgentVersion).filter(
AgentVersion.agent_id == agent_id,
AgentVersion.tenant_id == tenant_id,
AgentVersion.delete_flag == 'N',
).order_by(AgentVersion.version_no.desc()).all()

return [as_dict(v) for v in versions]
except Exception as e:
error_str = str(e).lower()
# If is_a2a column doesn't exist, retry with explicit column selection
if "is_a2a" in str(e) and ("does not exist" in error_str or "undefinedcolumn" in error_str):
with get_db_session() as session:
from sqlalchemy import select
columns = [
AgentVersion.id,
AgentVersion.tenant_id,
AgentVersion.agent_id,
AgentVersion.version_no,
AgentVersion.version_name,
AgentVersion.release_note,
AgentVersion.source_version_no,
AgentVersion.source_type,
AgentVersion.status,
AgentVersion.created_by,
AgentVersion.create_time,
]
versions = session.query(*columns).filter(
AgentVersion.agent_id == agent_id,
AgentVersion.tenant_id == tenant_id,
AgentVersion.delete_flag == 'N',
).order_by(AgentVersion.version_no.desc()).all()

return [dict(zip([c.key for c in columns], v)) for v in versions]
raise


def query_current_version_no(
Expand Down Expand Up @@ -141,11 +169,35 @@
Insert a new version metadata record
Returns: version id
"""
with get_db_session() as session:
result = session.execute(
insert(AgentVersion).values(**version_data).returning(AgentVersion.id)
)
return result.scalar_one()
from sqlalchemy import text

# First try with full data
try:
with get_db_session() as session:
result = session.execute(
insert(AgentVersion).values(**version_data).returning(AgentVersion.id)
)
return result.scalar_one()
except Exception as e:
error_str = str(e).lower()
# If is_a2a column doesn't exist, retry without it using native SQL
if "is_a2a" in str(e) and ("does not exist" in error_str or "undefinedcolumn" in error_str):
logger.info("is_a2a column not found, using native SQL to insert")
# Build column list and parameter placeholders
columns = [k for k in version_data.keys() if k != 'is_a2a']
col_list = ', '.join(columns)
placeholders = ', '.join([f':{c}' for c in columns])
insert_sql = text(f"""
INSERT INTO nexent.ag_tenant_agent_version_t (id, {col_list})
VALUES (nextval('nexent.ag_tenant_agent_version_t_id_seq'), {placeholders})
RETURNING id
""")

Check failure

Code scanning / CodeQL

SQL query built from user-controlled sources High

This SQL query depends on a
user-provided value
.
This SQL query depends on a
user-provided value
.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
# Build params without is_a2a
params = {k: v for k, v in version_data.items() if k != 'is_a2a'}
with get_db_session() as session:
result = session.execute(insert_sql, params)
return result.scalar_one()
raise


def update_version_status(
Expand Down
13 changes: 12 additions & 1 deletion backend/database/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,13 @@ def get_db_session(db_session=None):
except Exception as e:
if db_session is None:
session.rollback()
logger.error(f"Database operation failed: {str(e)}")
error_str = str(e).lower()
# For "is_a2a column does not exist" errors, just log warning and raise
# The caller should handle this by removing the field and retrying
if "is_a2a" in str(e) and "does not exist" in error_str:
logger.warning(f"Database operation failed (expected for missing is_a2a column): {str(e)}")
else:
logger.error(f"Database operation failed: {str(e)}")
raise e
finally:
if db_session is None:
Expand Down Expand Up @@ -373,6 +379,11 @@ def get_monitoring_db_session(db_session=None):
except Exception as e:
if db_session is None:
session.rollback()
# Silently ignore "table does not exist" errors for monitoring
# This allows the app to work even if the monitoring table hasn't been created yet
if "does not exist" in str(e).lower():
logger.warning(f"Monitoring table not found, skipping: {str(e)}")
return
logger.error(f"Monitoring database operation failed: {str(e)}")
raise
finally:
Expand Down
1 change: 0 additions & 1 deletion backend/database/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,6 @@ class AgentInfo(TableBase):
is_new = Column(Boolean, default=False, doc="Whether this agent is marked as new for the user")
current_version_no = Column(Integer, nullable=True, doc="Current published version number. NULL means no version published yet")
ingroup_permission = Column(String(30), doc="In-group permission: EDIT, READ_ONLY, PRIVATE")
enable_context_manager = Column(Boolean, default=False, doc="Whether to enable context management (compression) for this agent")


class ToolInstance(TableBase):
Expand Down
71 changes: 42 additions & 29 deletions backend/database/skill_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,40 +44,53 @@ def create_or_update_skill_by_skill_info(skill_info, tenant_id: str, user_id: st
skill_info_dict.setdefault("created_by", user_id)
skill_info_dict.setdefault("updated_by", user_id)

with get_db_session() as session:
query = session.query(SkillInstance).filter(
SkillInstance.tenant_id == tenant_id,
SkillInstance.agent_id == skill_info_dict.get('agent_id'),
SkillInstance.delete_flag != 'Y',
SkillInstance.skill_id == skill_info_dict.get('skill_id'),
SkillInstance.version_no == version_no
)
skill_instance = query.first()

if skill_instance:
for key, value in skill_info_dict.items():
if hasattr(skill_instance, key):
setattr(skill_instance, key, value)
else:
new_skill_instance = SkillInstance(
**filter_property(skill_info_dict, SkillInstance))
session.add(new_skill_instance)
session.flush()
skill_instance = new_skill_instance
try:
with get_db_session() as session:
query = session.query(SkillInstance).filter(
SkillInstance.tenant_id == tenant_id,
SkillInstance.agent_id == skill_info_dict.get('agent_id'),
SkillInstance.delete_flag != 'Y',
SkillInstance.skill_id == skill_info_dict.get('skill_id'),
SkillInstance.version_no == version_no
)
skill_instance = query.first()

if skill_instance:
for key, value in skill_info_dict.items():
if hasattr(skill_instance, key):
setattr(skill_instance, key, value)
else:
new_skill_instance = SkillInstance(
**filter_property(skill_info_dict, SkillInstance))
session.add(new_skill_instance)
session.flush()
skill_instance = new_skill_instance

return as_dict(skill_instance)
return as_dict(skill_instance)
except Exception as e:
# Return None if table doesn't exist (migration not applied)
if "relation" in str(e).lower() and "does not exist" in str(e).lower():
logger.warning(f"Skill instance table not found, skipping skill update: {e}")
return None
raise


def query_skill_instances_by_agent_id(agent_id: int, tenant_id: str, version_no: int = 0):
"""Query all SkillInstance for an agent (regardless of enabled status)."""
with get_db_session() as session:
query = session.query(SkillInstance).filter(
SkillInstance.tenant_id == tenant_id,
SkillInstance.agent_id == agent_id,
SkillInstance.version_no == version_no,
SkillInstance.delete_flag != 'Y')
skill_instances = query.all()
return [as_dict(skill_instance) for skill_instance in skill_instances]
try:
with get_db_session() as session:
query = session.query(SkillInstance).filter(
SkillInstance.tenant_id == tenant_id,
SkillInstance.agent_id == agent_id,
SkillInstance.version_no == version_no,
SkillInstance.delete_flag != 'Y')
skill_instances = query.all()
return [as_dict(skill_instance) for skill_instance in skill_instances]
except Exception as e:
# Return empty list if table doesn't exist (migration not applied)
if "relation" in str(e).lower() and "does not exist" in str(e).lower():
return []
raise


def query_enabled_skill_instances(agent_id: int, tenant_id: str, version_no: int = 0):
Expand Down
17 changes: 12 additions & 5 deletions backend/services/a2a_client_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,11 +420,18 @@ def list_external_agents(
Returns:
List of agent information dicts.
"""
return a2a_agent_db.list_external_agents(
tenant_id=tenant_id,
source_type=source_type,
is_available=is_available
)
try:
return a2a_agent_db.list_external_agents(
tenant_id=tenant_id,
source_type=source_type,
is_available=is_available
)
except Exception as e:
# Return empty list if table doesn't exist (migration not applied)
if "relation" in str(e).lower() and "does not exist" in str(e).lower():
logger.warning(f"A2A external agents table not found, returning empty list: {e}")
return []
raise

def update_agent_protocol(
self,
Expand Down
5 changes: 4 additions & 1 deletion backend/services/config_sync_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ async def save_config_impl(config, tenant_id, user_id):
continue

model_display_name = model_config.get("displayName")

config_key = get_env_key(model_type) + "_ID"

logger.info(f"Saving model config: type={model_type}, key={config_key}, displayName={model_display_name}")

model_id = get_model_id_by_display_name(
model_display_name, tenant_id)

Expand Down Expand Up @@ -171,6 +173,7 @@ def build_models_config(tenant_id: str) -> dict:
try:
model_config = tenant_config_manager.get_model_config(
config_key, tenant_id=tenant_id)
logger.info(f"build_models_config: key={model_key}, config_key={config_key}, model_config={model_config}")
models_config[model_key] = build_model_config(model_config)
except Exception as e:
logger.warning(f"Failed to get config for {config_key}: {e}")
Expand Down
9 changes: 8 additions & 1 deletion backend/services/image_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ async def proxy_image_impl(decoded_url: str):

def get_vlm_model(tenant_id: str):
# Get the tenant config
# First try imageUnderstanding (newer name), then fallback to vlm (legacy name)
vlm_model_config = tenant_config_manager.get_model_config(
key=MODEL_CONFIG_MAPPING["vlm"], tenant_id=tenant_id)
key=MODEL_CONFIG_MAPPING.get("imageUnderstanding", "IMAGE_UNDERSTANDING_ID"), tenant_id=tenant_id)

# If imageUnderstanding not found, try vlm (for backward compatibility)
if not vlm_model_config:
vlm_model_config = tenant_config_manager.get_model_config(
key=MODEL_CONFIG_MAPPING.get("vlm", "VLM_ID"), tenant_id=tenant_id)

if not vlm_model_config:
return None
return OpenAIVLModel(
Expand Down
Loading
Loading