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
13 changes: 13 additions & 0 deletions backend/apps/config_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from apps.model_managment_app import router as model_manager_router
from apps.oauth_app import router as oauth_router
from apps.prompt_app import router as prompt_router
from apps.prompt_template_app import router as prompt_template_router
from apps.remote_mcp_app import router as remote_mcp_router
from apps.skill_app import router as skill_router
from apps.tenant_config_app import router as tenant_config_router
Expand All @@ -30,13 +31,24 @@
from apps.monitoring_app import router as monitoring_router
from apps.a2a_server_app import router as a2a_server_router
from consts.const import IS_SPEED_MODE
from services.prompt_template_service import sync_system_default_prompt_template

# Create logger instance
logger = logging.getLogger("base_app")

# Create FastAPI app with common configurations
app = create_app(title="Nexent Config API", description="Configuration APIs")


@app.on_event("startup")
async def sync_default_prompt_template_on_startup():
"""Sync the YAML-backed system default prompt template into the database on startup."""
try:
sync_system_default_prompt_template()
logger.info("System default prompt template synced successfully.")
except Exception as exc:
logger.error(f"Failed to sync system default prompt template: {str(exc)}")

Check failure on line 50 in backend/apps/config_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ4WwhVFrxK1t71jPWqt&open=AZ4WwhVFrxK1t71jPWqt&pullRequest=2925

app.include_router(model_manager_router)
app.include_router(config_sync_router)
app.include_router(agent_router)
Expand All @@ -62,6 +74,7 @@

app.include_router(summary_router)
app.include_router(prompt_router)
app.include_router(prompt_template_router)
app.include_router(skill_router)
app.include_router(tenant_config_router)
app.include_router(remote_mcp_router)
Expand Down
1 change: 1 addition & 0 deletions backend/apps/prompt_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async def generate_and_save_system_prompt_api(
agent_id=prompt_request.agent_id,
model_id=prompt_request.model_id,
task_description=prompt_request.task_description,
prompt_template_id=prompt_request.prompt_template_id,
user_id=user_id,
tenant_id=tenant_id,
language=language,
Expand Down
143 changes: 143 additions & 0 deletions backend/apps/prompt_template_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import logging
from http import HTTPStatus
from typing import Optional

from fastapi import APIRouter, Header, HTTPException
from starlette.responses import JSONResponse

from consts.exceptions import DuplicateError, NotFoundException, ValidationError
from consts.model import PromptTemplateRequest
from services.prompt_template_service import (
create_prompt_template_impl,
delete_prompt_template_impl,
get_prompt_template_detail_impl,
list_prompt_templates_impl,
update_prompt_template_impl,
)
from utils.auth_utils import get_current_user_id

router = APIRouter(prefix="/prompt_templates")
logger = logging.getLogger("prompt_template_app")


@router.get("")
async def list_prompt_templates_api(
authorization: Optional[str] = Header(None),

Check warning on line 25 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ3sNs0-NwJKsMcWgZ0c&open=AZ3sNs0-NwJKsMcWgZ0c&pullRequest=2925
):
"""List prompt templates for the current user."""
try:
user_id, tenant_id = get_current_user_id(authorization)
result = list_prompt_templates_impl(tenant_id=tenant_id, user_id=user_id)
return JSONResponse(status_code=HTTPStatus.OK, content=result)
except Exception as exc:
logger.error(f"Prompt template list error: {str(exc)}")

Check failure on line 33 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ4WwhgnrxK1t71jPWqu&open=AZ4WwhgnrxK1t71jPWqu&pullRequest=2925
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Prompt template list error.",
)


@router.get("/{template_id}")
async def get_prompt_template_api(
template_id: int,
authorization: Optional[str] = Header(None),

Check warning on line 43 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ3sNs0-NwJKsMcWgZ0d&open=AZ3sNs0-NwJKsMcWgZ0d&pullRequest=2925
):
"""Get prompt template detail."""
try:
user_id, tenant_id = get_current_user_id(authorization)
result = get_prompt_template_detail_impl(
template_id=template_id,
tenant_id=tenant_id,
user_id=user_id,
)
return JSONResponse(status_code=HTTPStatus.OK, content=result)
except NotFoundException as exc:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail=str(exc))
except Exception as exc:
logger.error(f"Prompt template detail error: {str(exc)}")

Check failure on line 57 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ4WwhgnrxK1t71jPWqv&open=AZ4WwhgnrxK1t71jPWqv&pullRequest=2925
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Prompt template detail error.",
)


@router.post("")
async def create_prompt_template_api(
request: PromptTemplateRequest,
authorization: Optional[str] = Header(None),

Check warning on line 67 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ3sNs0-NwJKsMcWgZ0e&open=AZ3sNs0-NwJKsMcWgZ0e&pullRequest=2925
):
"""Create a prompt template."""
try:
user_id, tenant_id = get_current_user_id(authorization)
result = create_prompt_template_impl(
request=request,
tenant_id=tenant_id,
user_id=user_id,
)
return JSONResponse(status_code=HTTPStatus.OK, content=result)
except DuplicateError as exc:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(exc))
except ValidationError as exc:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(exc))
except Exception as exc:
logger.error(f"Prompt template create error: {str(exc)}")

Check failure on line 83 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ4WwhgnrxK1t71jPWqw&open=AZ4WwhgnrxK1t71jPWqw&pullRequest=2925
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Prompt template create error.",
)


@router.put("/{template_id}")
async def update_prompt_template_api(
template_id: int,
request: PromptTemplateRequest,
authorization: Optional[str] = Header(None),

Check warning on line 94 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ3sNs0-NwJKsMcWgZ0f&open=AZ3sNs0-NwJKsMcWgZ0f&pullRequest=2925
):
"""Update a prompt template."""
try:
user_id, tenant_id = get_current_user_id(authorization)
result = update_prompt_template_impl(
template_id=template_id,
request=request,
tenant_id=tenant_id,
user_id=user_id,
)
return JSONResponse(status_code=HTTPStatus.OK, content=result)
except NotFoundException as exc:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail=str(exc))
except DuplicateError as exc:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(exc))
except ValidationError as exc:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(exc))
except Exception as exc:
logger.error(f"Prompt template update error: {str(exc)}")

Check failure on line 113 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ4WwhgnrxK1t71jPWqx&open=AZ4WwhgnrxK1t71jPWqx&pullRequest=2925
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Prompt template update error.",
)


@router.delete("/{template_id}")
async def delete_prompt_template_api(
template_id: int,
authorization: Optional[str] = Header(None),

Check warning on line 123 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "Annotated" type hints for FastAPI dependency injection

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ3sNs0-NwJKsMcWgZ0g&open=AZ3sNs0-NwJKsMcWgZ0g&pullRequest=2925
):
"""Delete a prompt template."""
try:
user_id, tenant_id = get_current_user_id(authorization)
result = delete_prompt_template_impl(
template_id=template_id,
tenant_id=tenant_id,
user_id=user_id,
)
return JSONResponse(status_code=HTTPStatus.OK, content=result)
except NotFoundException as exc:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail=str(exc))
except ValidationError as exc:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(exc))
except Exception as exc:
logger.error(f"Prompt template delete error: {str(exc)}")

Check failure on line 139 in backend/apps/prompt_template_app.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use "logging.exception()" instead.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ4WwhgnrxK1t71jPWqy&open=AZ4WwhgnrxK1t71jPWqy&pullRequest=2925
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Prompt template delete error.",
)
55 changes: 54 additions & 1 deletion backend/consts/model.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from enum import Enum
from typing import Optional, Any, List, Dict

from pydantic import BaseModel, Field, EmailStr
from pydantic import BaseModel, Field, EmailStr, ConfigDict
from nexent.core.agents.agent_model import ToolConfig

from consts.prompt_template import PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP


class ModelConnectStatusEnum(Enum):
"""Enum class for model connection status"""
Expand Down Expand Up @@ -312,6 +314,7 @@ class GeneratePromptRequest(BaseModel):
task_description: str
agent_id: int
model_id: int
prompt_template_id: Optional[int] = None
tool_ids: Optional[List[int]] = Field(
None, description="Optional: tool IDs from frontend (takes precedence over database query)")
sub_agent_ids: Optional[List[int]] = Field(
Expand All @@ -320,6 +323,52 @@ class GeneratePromptRequest(BaseModel):
None, description="Optional: knowledge base display names from frontend (takes precedence over database query)")


class PromptTemplateContentRequest(BaseModel):
model_config = ConfigDict(populate_by_name=True)

duty_system_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["duty_system_prompt"]
)
constraint_system_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["constraint_system_prompt"]
)
few_shots_system_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["few_shots_system_prompt"]
)
agent_variable_name_system_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["agent_variable_name_system_prompt"]
)
agent_display_name_system_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["agent_display_name_system_prompt"]
)
agent_description_system_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["agent_description_system_prompt"]
)
user_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["user_prompt"]
)
agent_name_regenerate_system_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["agent_name_regenerate_system_prompt"]
)
agent_name_regenerate_user_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["agent_name_regenerate_user_prompt"]
)
agent_display_name_regenerate_system_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["agent_display_name_regenerate_system_prompt"]
)
agent_display_name_regenerate_user_prompt: str = Field(
alias=PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP["agent_display_name_regenerate_user_prompt"]
)


class PromptTemplateRequest(BaseModel):
template_name: str
description: Optional[str] = None
template_type: str = "agent_generate"
template_content_zh: PromptTemplateContentRequest
template_content_en: Optional[PromptTemplateContentRequest] = None


class GenerateTitleRequest(BaseModel):
conversation_id: int
question: str
Expand All @@ -343,6 +392,8 @@ class AgentInfoRequest(BaseModel):
enabled: Optional[bool] = None
business_logic_model_name: Optional[str] = None
business_logic_model_id: Optional[int] = None
prompt_template_id: Optional[int] = None
prompt_template_name: Optional[str] = None
enabled_tool_ids: Optional[List[int]] = None
enabled_skill_ids: Optional[List[int]] = None
related_agent_ids: Optional[List[int]] = None
Expand Down Expand Up @@ -431,6 +482,8 @@ class ExportAndImportAgentInfo(BaseModel):
model_name: Optional[str] = None
business_logic_model_id: Optional[int] = None
business_logic_model_name: Optional[str] = None
prompt_template_id: Optional[int] = None
prompt_template_name: Optional[str] = None

class Config:
arbitrary_types_allowed = True
Expand Down
15 changes: 15 additions & 0 deletions backend/consts/prompt_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP = {
"duty_system_prompt": "DUTY_SYSTEM_PROMPT",
"constraint_system_prompt": "CONSTRAINT_SYSTEM_PROMPT",
"few_shots_system_prompt": "FEW_SHOTS_SYSTEM_PROMPT",
"agent_variable_name_system_prompt": "AGENT_VARIABLE_NAME_SYSTEM_PROMPT",
"agent_display_name_system_prompt": "AGENT_DISPLAY_NAME_SYSTEM_PROMPT",
"agent_description_system_prompt": "AGENT_DESCRIPTION_SYSTEM_PROMPT",
"user_prompt": "USER_PROMPT",
"agent_name_regenerate_system_prompt": "AGENT_NAME_REGENERATE_SYSTEM_PROMPT",
"agent_name_regenerate_user_prompt": "AGENT_NAME_REGENERATE_USER_PROMPT",
"agent_display_name_regenerate_system_prompt": "AGENT_DISPLAY_NAME_REGENERATE_SYSTEM_PROMPT",
"agent_display_name_regenerate_user_prompt": "AGENT_DISPLAY_NAME_REGENERATE_USER_PROMPT",
}

PROMPT_GENERATE_TEMPLATE_FIELDS = tuple(PROMPT_GENERATE_TEMPLATE_FIELD_ALIAS_MAP.keys())
2 changes: 2 additions & 0 deletions backend/database/agent_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ def create_agent(agent_info, tenant_id: str, user_id: str):
"business_description": new_agent.business_description,
"business_logic_model_id": new_agent.business_logic_model_id,
"business_logic_model_name": new_agent.business_logic_model_name,
"prompt_template_id": new_agent.prompt_template_id,
"prompt_template_name": new_agent.prompt_template_name,
"group_ids": new_agent.group_ids,
"is_new": new_agent.is_new,
"enable_context_manager": new_agent.enable_context_manager,
Expand Down
38 changes: 38 additions & 0 deletions backend/database/db_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from sqlalchemy import BigInteger, Boolean, Column, ForeignKey, ForeignKeyConstraint, Integer, JSON, Numeric, PrimaryKeyConstraint, Sequence, String, Text, TIMESTAMP, UniqueConstraint, Index, Float, text
from sqlalchemy import BigInteger, Boolean, Column, Integer, JSON, Numeric, Sequence, String, Text, TIMESTAMP, UniqueConstraint, Index, Float
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import DeclarativeBase
Expand Down Expand Up @@ -313,13 +314,50 @@
Text, doc="Manually entered by the user to describe the entire business process")
business_logic_model_name = Column(String(100), doc="Model name used for business logic prompt generation")
business_logic_model_id = Column(Integer, doc="Model ID used for business logic prompt generation, foreign key reference to model_record_t.model_id")
prompt_template_id = Column(Integer, doc="Prompt template ID used for business logic prompt generation")
prompt_template_name = Column(String(100), doc="Prompt template name used for business logic prompt generation")
group_ids = Column(String, doc="Agent group IDs list")
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 PromptTemplate(TableBase):
"""
Prompt template table for user-defined prompt generation templates.
"""
__tablename__ = "ag_prompt_template_t"
__table_args__ = (
Index(
"uq_prompt_template_user_name_active",
"tenant_id",
"user_id",
"template_name",
unique=True,
postgresql_where=text("delete_flag = 'N'"),
),
Index(
"idx_ag_prompt_template_t_user",
"tenant_id",
"user_id",
"template_type",
postgresql_where=text("delete_flag = 'N'"),
),
{"schema": SCHEMA},
)

template_id = Column(Integer, Sequence(
"ag_prompt_template_t_template_id_seq", schema=SCHEMA), primary_key=True, nullable=False, autoincrement=True, doc="Prompt template ID")
template_name = Column(String(100), nullable=False, doc="Prompt template name")
description = Column(String(500), doc="Prompt template description")
template_type = Column(String(50), nullable=False, default="agent_generate", doc="Prompt template type")
tenant_id = Column(String(100), nullable=False, doc="Tenant ID")

Check failure on line 355 in backend/database/db_models.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "Tenant ID" 11 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ3sNstaNwJKsMcWgZ0b&open=AZ3sNstaNwJKsMcWgZ0b&pullRequest=2925
user_id = Column(String(100), nullable=False, doc="User ID")

Check failure on line 356 in backend/database/db_models.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "User ID" 9 times.

See more on https://sonarcloud.io/project/issues?id=ModelEngine-Group_nexent&issues=AZ3sNstaNwJKsMcWgZ0a&open=AZ3sNstaNwJKsMcWgZ0a&pullRequest=2925
template_content_zh = Column(JSONB, nullable=False, doc="Chinese prompt template content")
template_content_en = Column(JSONB, doc="English prompt template content")


class ToolInstance(TableBase):
"""
Information table for tenant tool configuration.
Expand Down
Loading
Loading