feat(agent): persist production generation settings

This commit is contained in:
2026-07-16 16:07:11 +08:00
parent 245b6b3eca
commit 6c02662d3b
9 changed files with 353 additions and 56 deletions

View File

@@ -13,6 +13,7 @@ from app.models.ai_config import ModelConfig, Prompt, SystemConfig
from app.models.knowledge import Knowledge
from app.schemas.admin import (
AgentDebugRequest,
AgentRuntimeConfigSaveRequest,
EnableModelRequest,
ModelSaveRequest,
PromptSaveRequest,
@@ -184,6 +185,46 @@ async def debug_agent(
return api_success(result)
@router.get("/agent/runtime-config")
def get_agent_runtime_config(
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
model = _enabled_model(db)
return api_success(_agent_runtime_config_dict(model))
@router.put("/agent/runtime-config")
def save_agent_runtime_config(
payload: AgentRuntimeConfigSaveRequest,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
model = _enabled_model(db)
if model is None:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="当前没有已启用模型,请先在模型管理中启用一个模型",
)
model.temperature = payload.temperature
model.top_p = payload.topP
model.top_k = payload.topK
model.presence_penalty = payload.presencePenalty
model.frequency_penalty = payload.frequencyPenalty
model.max_token = payload.maxToken
db.add(model)
OperationLogService.write(
db,
admin_id=current_admin.id,
module="agent",
action="save_runtime_config",
target_id=model.id,
)
db.commit()
db.refresh(model)
return api_success(_agent_runtime_config_dict(model))
@router.get("/model/list")
def list_models(db: Session = Depends(get_db), current_admin: Admin = Depends(get_current_admin)) -> dict:
models = db.scalars(select(ModelConfig).order_by(ModelConfig.id.desc())).all()
@@ -376,6 +417,28 @@ def _model_dict(model: ModelConfig) -> dict:
}
def _enabled_model(db: Session) -> ModelConfig | None:
return db.scalar(
select(ModelConfig)
.where(ModelConfig.enabled == 1)
.order_by(ModelConfig.id.desc())
.limit(1)
)
def _agent_runtime_config_dict(model: ModelConfig | None) -> dict:
return {
"modelId": model.id if model is not None else None,
"modelName": (model.display_name or model.model_name) if model is not None else None,
"temperature": float(model.temperature) if model is not None and model.temperature is not None else None,
"topP": float(model.top_p) if model is not None and model.top_p is not None else None,
"topK": model.top_k if model is not None else None,
"presencePenalty": float(model.presence_penalty) if model is not None and model.presence_penalty is not None else None,
"frequencyPenalty": float(model.frequency_penalty) if model is not None and model.frequency_penalty is not None else None,
"maxToken": model.max_token if model is not None and model.max_token is not None else 8192,
}
def _default_prompt_detail() -> dict:
return {
"id": None,

View File

@@ -89,7 +89,16 @@ class AgentDebugRequest(BaseModel):
topK: int | None = Field(default=None, ge=1, le=1000)
presencePenalty: float | None = Field(default=None, ge=-2, le=2)
frequencyPenalty: float | None = Field(default=None, ge=-2, le=2)
maxToken: int | None = Field(default=1024, ge=1, le=100000)
maxToken: int | None = Field(default=8192, ge=1, le=100000)
class AgentRuntimeConfigSaveRequest(BaseModel):
temperature: float | None = Field(default=None, ge=0, le=2)
topP: float | None = Field(default=None, ge=0, le=1)
topK: int | None = Field(default=None, ge=1, le=1000)
presencePenalty: float | None = Field(default=None, ge=-2, le=2)
frequencyPenalty: float | None = Field(default=None, ge=-2, le=2)
maxToken: int = Field(default=8192, ge=256, le=100000)
class ModelSaveRequest(BaseModel):

View File

@@ -494,7 +494,7 @@ def _agent_system_prompt() -> str:
def _max_output_tokens(model: ModelConfig) -> int:
configured = max(1, model.max_token or 1024)
configured = max(1, model.max_token or 8192)
if not model.context_window:
return configured
return min(configured, max(1, model.context_window // 3))

View File

@@ -0,0 +1,101 @@
from decimal import Decimal
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool
from app.api.admin_settings import get_agent_runtime_config, save_agent_runtime_config
from app.models import Base
from app.models.admin import Admin
from app.models.ai_config import ModelConfig
from app.schemas.admin import AgentRuntimeConfigSaveRequest
from app.services.model_stream_service import _openai_stream_payload
from app.services.model_service import _max_output_tokens
from app.services.rag_service import RagResult
def _database() -> Session:
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
Base.metadata.create_all(engine)
return Session(engine)
def _admin() -> Admin:
return Admin(id=1, username="admin", password="hash", name="系统管理员", status=1)
def _model() -> ModelConfig:
return ModelConfig(
id=1,
provider="openai-compatible",
display_name="正式模型",
api_type="openai_compatible",
model_name="production-model",
base_url="https://example.com/v1",
api_url="",
api_key="encrypted",
auth_type="bearer",
max_token=None,
stream_enabled=1,
timeout_second=30,
enabled=1,
)
def test_runtime_config_defaults_to_long_answer_safe_max_tokens():
with _database() as db:
admin = _admin()
model = _model()
db.add_all([admin, model])
db.commit()
result = get_agent_runtime_config(db=db, current_admin=admin)["data"]
assert result["modelId"] == model.id
assert result["modelName"] == "正式模型"
assert result["maxToken"] == 8192
assert _max_output_tokens(model) == 8192
def test_saved_runtime_config_is_persisted_on_enabled_model():
with _database() as db:
admin = _admin()
model = _model()
db.add_all([admin, model])
db.commit()
result = save_agent_runtime_config(
AgentRuntimeConfigSaveRequest(
temperature=0.3,
topP=0.9,
topK=40,
presencePenalty=0.2,
frequencyPenalty=0.4,
maxToken=12000,
),
db=db,
current_admin=admin,
)["data"]
db.refresh(model)
assert result["maxToken"] == 12000
assert model.max_token == 12000
assert model.temperature == Decimal("0.30")
assert model.top_p == Decimal("0.90")
assert model.top_k == 40
assert model.presence_penalty == Decimal("0.20")
assert model.frequency_penalty == Decimal("0.40")
payload = _openai_stream_payload(
model,
RagResult(question="测试", knowledge_scopes=[], chunks=[], prompt="测试", allow_general_knowledge=True),
)
assert payload["max_tokens"] == 12000
assert payload["temperature"] == 0.3
assert payload["top_p"] == 0.9
assert payload["presence_penalty"] == 0.2
assert payload["frequency_penalty"] == 0.4