Skip to content
Open
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
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 @@ -154,6 +154,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 @@ -286,7 +286,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
4 changes: 3 additions & 1 deletion backend/consts/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ class ModelConfig(BaseModel):
vlm: SingleModelConfig
stt: SingleModelConfig
tts: SingleModelConfig
imageUnderstanding: SingleModelConfig
imageGeneration: SingleModelConfig
videoUnderstanding: SingleModelConfig


class AppConfig(BaseModel):
Expand Down Expand Up @@ -336,7 +339,6 @@ class AgentInfoRequest(BaseModel):
related_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 @@ -1214,12 +1214,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 @@ def query_version_list(
"""
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 @@ def insert_version(
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
""")
# 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 @@ -313,7 +313,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 @@ -369,11 +369,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 @@ -156,6 +158,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