|
|
|
|
@@ -8,6 +8,7 @@ import httpx
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
|
from fastapi.responses import FileResponse
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
from sqlalchemy import inspect, text
|
|
|
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
|
import uuid
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
@@ -35,6 +36,14 @@ router = APIRouter(prefix="/assistants", tags=["Assistants"])
|
|
|
|
|
OPENAI_COMPATIBLE_DEFAULT_MODEL = "FunAudioLLM/CosyVoice2-0.5B"
|
|
|
|
|
OPENAI_COMPATIBLE_DEFAULT_BASE_URL = "https://api.siliconflow.cn/v1"
|
|
|
|
|
OPENER_AUDIO_DIR = Path(__file__).resolve().parents[2] / "data" / "opener_audio"
|
|
|
|
|
PRESENCE_PROBE_MIN_IDLE_SECONDS = 5.0
|
|
|
|
|
PRESENCE_PROBE_MAX_IDLE_SECONDS = 3600.0
|
|
|
|
|
PRESENCE_PROBE_DEFAULT_IDLE_SECONDS = 20.0
|
|
|
|
|
PRESENCE_PROBE_MIN_COOLDOWN_SECONDS = 5.0
|
|
|
|
|
PRESENCE_PROBE_MAX_COOLDOWN_SECONDS = 7200.0
|
|
|
|
|
PRESENCE_PROBE_DEFAULT_COOLDOWN_SECONDS = 45.0
|
|
|
|
|
PRESENCE_PROBE_MAX_PROMPTS_CAP = 10
|
|
|
|
|
PRESENCE_PROBE_DEFAULT_MAX_PROMPTS = 2
|
|
|
|
|
OPENAI_COMPATIBLE_KNOWN_VOICES = {
|
|
|
|
|
"alex",
|
|
|
|
|
"anna",
|
|
|
|
|
@@ -85,6 +94,125 @@ def _config_version_id(assistant: Assistant) -> str:
|
|
|
|
|
return f"asst_{assistant.id}_{updated.strftime('%Y%m%d%H%M%S')}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_assistant_schema(db: Session) -> None:
|
|
|
|
|
"""Apply lightweight SQLite migrations for newly added assistant columns."""
|
|
|
|
|
bind = db.get_bind()
|
|
|
|
|
inspector = inspect(bind)
|
|
|
|
|
try:
|
|
|
|
|
columns = {col["name"] for col in inspector.get_columns("assistants")}
|
|
|
|
|
except Exception:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
altered = False
|
|
|
|
|
if "presence_probe_enabled" not in columns:
|
|
|
|
|
db.execute(text("ALTER TABLE assistants ADD COLUMN presence_probe_enabled BOOLEAN DEFAULT 0"))
|
|
|
|
|
altered = True
|
|
|
|
|
if "presence_probe_idle_seconds" not in columns:
|
|
|
|
|
db.execute(
|
|
|
|
|
text(
|
|
|
|
|
"ALTER TABLE assistants ADD COLUMN presence_probe_idle_seconds FLOAT DEFAULT 20.0"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
altered = True
|
|
|
|
|
if "presence_probe_cooldown_seconds" not in columns:
|
|
|
|
|
db.execute(
|
|
|
|
|
text(
|
|
|
|
|
"ALTER TABLE assistants ADD COLUMN presence_probe_cooldown_seconds FLOAT DEFAULT 45.0"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
altered = True
|
|
|
|
|
if "presence_probe_max_prompts" not in columns:
|
|
|
|
|
db.execute(text("ALTER TABLE assistants ADD COLUMN presence_probe_max_prompts INTEGER DEFAULT 2"))
|
|
|
|
|
altered = True
|
|
|
|
|
if "presence_probe_include_context" not in columns:
|
|
|
|
|
db.execute(
|
|
|
|
|
text("ALTER TABLE assistants ADD COLUMN presence_probe_include_context BOOLEAN DEFAULT 1")
|
|
|
|
|
)
|
|
|
|
|
altered = True
|
|
|
|
|
if "presence_probe_question" not in columns:
|
|
|
|
|
db.execute(text("ALTER TABLE assistants ADD COLUMN presence_probe_question TEXT DEFAULT ''"))
|
|
|
|
|
altered = True
|
|
|
|
|
if altered:
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _coerce_bounded_float(
|
|
|
|
|
raw_value: Any,
|
|
|
|
|
*,
|
|
|
|
|
default_value: float,
|
|
|
|
|
min_value: float,
|
|
|
|
|
max_value: float,
|
|
|
|
|
) -> float:
|
|
|
|
|
if isinstance(raw_value, (int, float)):
|
|
|
|
|
parsed = float(raw_value)
|
|
|
|
|
elif isinstance(raw_value, str):
|
|
|
|
|
try:
|
|
|
|
|
parsed = float(raw_value.strip())
|
|
|
|
|
except ValueError:
|
|
|
|
|
parsed = default_value
|
|
|
|
|
else:
|
|
|
|
|
parsed = default_value
|
|
|
|
|
if parsed < min_value:
|
|
|
|
|
return min_value
|
|
|
|
|
if parsed > max_value:
|
|
|
|
|
return max_value
|
|
|
|
|
return parsed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _coerce_bounded_int(
|
|
|
|
|
raw_value: Any,
|
|
|
|
|
*,
|
|
|
|
|
default_value: int,
|
|
|
|
|
min_value: int,
|
|
|
|
|
max_value: int,
|
|
|
|
|
) -> int:
|
|
|
|
|
if isinstance(raw_value, (int, float)):
|
|
|
|
|
parsed = int(raw_value)
|
|
|
|
|
elif isinstance(raw_value, str):
|
|
|
|
|
try:
|
|
|
|
|
parsed = int(raw_value.strip())
|
|
|
|
|
except ValueError:
|
|
|
|
|
parsed = default_value
|
|
|
|
|
else:
|
|
|
|
|
parsed = default_value
|
|
|
|
|
if parsed < min_value:
|
|
|
|
|
return min_value
|
|
|
|
|
if parsed > max_value:
|
|
|
|
|
return max_value
|
|
|
|
|
return parsed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_presence_probe_config_from_assistant(assistant: Assistant) -> Dict[str, Any]:
|
|
|
|
|
question = str(assistant.presence_probe_question or "").strip()
|
|
|
|
|
if len(question) > 160:
|
|
|
|
|
question = question[:160]
|
|
|
|
|
include_context_raw = getattr(assistant, "presence_probe_include_context", True)
|
|
|
|
|
include_context = True if include_context_raw is None else bool(include_context_raw)
|
|
|
|
|
return {
|
|
|
|
|
"enabled": bool(assistant.presence_probe_enabled),
|
|
|
|
|
"idleSeconds": _coerce_bounded_float(
|
|
|
|
|
assistant.presence_probe_idle_seconds,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_IDLE_SECONDS,
|
|
|
|
|
min_value=PRESENCE_PROBE_MIN_IDLE_SECONDS,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_IDLE_SECONDS,
|
|
|
|
|
),
|
|
|
|
|
"cooldownSeconds": _coerce_bounded_float(
|
|
|
|
|
assistant.presence_probe_cooldown_seconds,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_COOLDOWN_SECONDS,
|
|
|
|
|
min_value=PRESENCE_PROBE_MIN_COOLDOWN_SECONDS,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_COOLDOWN_SECONDS,
|
|
|
|
|
),
|
|
|
|
|
"maxPrompts": _coerce_bounded_int(
|
|
|
|
|
assistant.presence_probe_max_prompts,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_MAX_PROMPTS,
|
|
|
|
|
min_value=1,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_PROMPTS_CAP,
|
|
|
|
|
),
|
|
|
|
|
"includeContext": include_context,
|
|
|
|
|
"question": question,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_runtime_tool_schema(tool_id: str, raw_schema: Any) -> Dict[str, Any]:
|
|
|
|
|
schema = dict(raw_schema) if isinstance(raw_schema, dict) else {}
|
|
|
|
|
if not schema:
|
|
|
|
|
@@ -182,6 +310,7 @@ def _resolve_runtime_tools(db: Session, selected_tool_ids: List[str], warnings:
|
|
|
|
|
|
|
|
|
|
def _resolve_runtime_metadata(db: Session, assistant: Assistant) -> tuple[Dict[str, Any], List[str]]:
|
|
|
|
|
warnings: List[str] = []
|
|
|
|
|
presence_probe_cfg = _resolve_presence_probe_config_from_assistant(assistant)
|
|
|
|
|
metadata: Dict[str, Any] = {
|
|
|
|
|
"systemPrompt": _compose_runtime_system_prompt(assistant.prompt),
|
|
|
|
|
"firstTurnMode": assistant.first_turn_mode or "bot_first",
|
|
|
|
|
@@ -199,6 +328,20 @@ def _resolve_runtime_metadata(db: Session, assistant: Assistant) -> tuple[Dict[s
|
|
|
|
|
"userId": int(assistant.user_id or 1),
|
|
|
|
|
"source": "debug",
|
|
|
|
|
},
|
|
|
|
|
"presenceProbe": {
|
|
|
|
|
"enabled": bool(presence_probe_cfg.get("enabled")),
|
|
|
|
|
"idleSeconds": float(presence_probe_cfg.get("idleSeconds") or PRESENCE_PROBE_DEFAULT_IDLE_SECONDS),
|
|
|
|
|
"cooldownSeconds": float(
|
|
|
|
|
presence_probe_cfg.get("cooldownSeconds") or PRESENCE_PROBE_DEFAULT_COOLDOWN_SECONDS
|
|
|
|
|
),
|
|
|
|
|
"maxPrompts": int(presence_probe_cfg.get("maxPrompts") or PRESENCE_PROBE_DEFAULT_MAX_PROMPTS),
|
|
|
|
|
"includeContext": bool(presence_probe_cfg.get("includeContext", True)),
|
|
|
|
|
**(
|
|
|
|
|
{"question": str(presence_probe_cfg.get("question") or "")}
|
|
|
|
|
if str(presence_probe_cfg.get("question") or "").strip()
|
|
|
|
|
else {}
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config_mode = str(assistant.config_mode or "platform").strip().lower()
|
|
|
|
|
@@ -321,6 +464,7 @@ def _build_engine_assistant_config(db: Session, assistant: Assistant) -> Dict[st
|
|
|
|
|
def assistant_to_dict(assistant: Assistant) -> dict:
|
|
|
|
|
opener_audio = assistant.opener_audio
|
|
|
|
|
opener_audio_ready = bool(opener_audio and opener_audio.file_path and Path(opener_audio.file_path).exists())
|
|
|
|
|
presence_probe_cfg = _resolve_presence_probe_config_from_assistant(assistant)
|
|
|
|
|
return {
|
|
|
|
|
"id": assistant.id,
|
|
|
|
|
"name": assistant.name,
|
|
|
|
|
@@ -342,6 +486,18 @@ def assistant_to_dict(assistant: Assistant) -> dict:
|
|
|
|
|
"tools": assistant.tools or [],
|
|
|
|
|
"botCannotBeInterrupted": bool(assistant.bot_cannot_be_interrupted),
|
|
|
|
|
"interruptionSensitivity": assistant.interruption_sensitivity,
|
|
|
|
|
"presenceProbeEnabled": bool(presence_probe_cfg.get("enabled")),
|
|
|
|
|
"presenceProbeIdleSeconds": float(
|
|
|
|
|
presence_probe_cfg.get("idleSeconds") or PRESENCE_PROBE_DEFAULT_IDLE_SECONDS
|
|
|
|
|
),
|
|
|
|
|
"presenceProbeCooldownSeconds": float(
|
|
|
|
|
presence_probe_cfg.get("cooldownSeconds") or PRESENCE_PROBE_DEFAULT_COOLDOWN_SECONDS
|
|
|
|
|
),
|
|
|
|
|
"presenceProbeMaxPrompts": int(
|
|
|
|
|
presence_probe_cfg.get("maxPrompts") or PRESENCE_PROBE_DEFAULT_MAX_PROMPTS
|
|
|
|
|
),
|
|
|
|
|
"presenceProbeIncludeContext": bool(presence_probe_cfg.get("includeContext", True)),
|
|
|
|
|
"presenceProbeQuestion": str(presence_probe_cfg.get("question") or ""),
|
|
|
|
|
"configMode": assistant.config_mode,
|
|
|
|
|
"apiUrl": assistant.api_url,
|
|
|
|
|
"apiKey": assistant.api_key,
|
|
|
|
|
@@ -360,6 +516,12 @@ def _apply_assistant_update(assistant: Assistant, update_data: dict) -> None:
|
|
|
|
|
"firstTurnMode": "first_turn_mode",
|
|
|
|
|
"interruptionSensitivity": "interruption_sensitivity",
|
|
|
|
|
"botCannotBeInterrupted": "bot_cannot_be_interrupted",
|
|
|
|
|
"presenceProbeEnabled": "presence_probe_enabled",
|
|
|
|
|
"presenceProbeIdleSeconds": "presence_probe_idle_seconds",
|
|
|
|
|
"presenceProbeCooldownSeconds": "presence_probe_cooldown_seconds",
|
|
|
|
|
"presenceProbeMaxPrompts": "presence_probe_max_prompts",
|
|
|
|
|
"presenceProbeIncludeContext": "presence_probe_include_context",
|
|
|
|
|
"presenceProbeQuestion": "presence_probe_question",
|
|
|
|
|
"configMode": "config_mode",
|
|
|
|
|
"voiceOutputEnabled": "voice_output_enabled",
|
|
|
|
|
"generatedOpenerEnabled": "generated_opener_enabled",
|
|
|
|
|
@@ -371,7 +533,37 @@ def _apply_assistant_update(assistant: Assistant, update_data: dict) -> None:
|
|
|
|
|
"rerankModelId": "rerank_model_id",
|
|
|
|
|
}
|
|
|
|
|
for field, value in update_data.items():
|
|
|
|
|
setattr(assistant, field_map.get(field, field), value)
|
|
|
|
|
target = field_map.get(field, field)
|
|
|
|
|
if target == "presence_probe_idle_seconds":
|
|
|
|
|
value = _coerce_bounded_float(
|
|
|
|
|
value,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_IDLE_SECONDS,
|
|
|
|
|
min_value=PRESENCE_PROBE_MIN_IDLE_SECONDS,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_IDLE_SECONDS,
|
|
|
|
|
)
|
|
|
|
|
elif target == "presence_probe_cooldown_seconds":
|
|
|
|
|
value = _coerce_bounded_float(
|
|
|
|
|
value,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_COOLDOWN_SECONDS,
|
|
|
|
|
min_value=PRESENCE_PROBE_MIN_COOLDOWN_SECONDS,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_COOLDOWN_SECONDS,
|
|
|
|
|
)
|
|
|
|
|
elif target == "presence_probe_max_prompts":
|
|
|
|
|
value = _coerce_bounded_int(
|
|
|
|
|
value,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_MAX_PROMPTS,
|
|
|
|
|
min_value=1,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_PROMPTS_CAP,
|
|
|
|
|
)
|
|
|
|
|
elif target == "presence_probe_question":
|
|
|
|
|
value = str(value or "").strip()
|
|
|
|
|
if len(value) > 160:
|
|
|
|
|
value = value[:160]
|
|
|
|
|
elif target == "presence_probe_enabled":
|
|
|
|
|
value = bool(value)
|
|
|
|
|
elif target == "presence_probe_include_context":
|
|
|
|
|
value = bool(value)
|
|
|
|
|
setattr(assistant, target, value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_assistant_opener_audio(db: Session, assistant: Assistant) -> AssistantOpenerAudio:
|
|
|
|
|
@@ -490,6 +682,7 @@ def list_assistants(
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
):
|
|
|
|
|
"""获取助手列表"""
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
query = db.query(Assistant)
|
|
|
|
|
total = query.count()
|
|
|
|
|
assistants = query.order_by(Assistant.created_at.desc()) \
|
|
|
|
|
@@ -505,6 +698,7 @@ def list_assistants(
|
|
|
|
|
@router.get("/{id}", response_model=AssistantOut)
|
|
|
|
|
def get_assistant(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
"""获取单个助手详情"""
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = db.query(Assistant).filter(Assistant.id == id).first()
|
|
|
|
|
if not assistant:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
|
|
|
@@ -514,6 +708,7 @@ def get_assistant(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
@router.get("/{id}/config", response_model=AssistantEngineConfigResponse)
|
|
|
|
|
def get_assistant_config(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
"""Canonical engine config endpoint consumed by engine backend adapter."""
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = db.query(Assistant).filter(Assistant.id == id).first()
|
|
|
|
|
if not assistant:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
|
|
|
@@ -523,6 +718,7 @@ def get_assistant_config(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
@router.get("/{id}/runtime-config", response_model=AssistantEngineConfigResponse)
|
|
|
|
|
def get_assistant_runtime_config(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
"""Legacy alias for resolved engine runtime config."""
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = db.query(Assistant).filter(Assistant.id == id).first()
|
|
|
|
|
if not assistant:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
|
|
|
@@ -532,6 +728,7 @@ def get_assistant_runtime_config(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
@router.post("", response_model=AssistantOut)
|
|
|
|
|
def create_assistant(data: AssistantCreate, db: Session = Depends(get_db)):
|
|
|
|
|
"""创建新助手"""
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = Assistant(
|
|
|
|
|
id=str(uuid.uuid4())[:8],
|
|
|
|
|
user_id=1, # 默认用户,后续添加认证
|
|
|
|
|
@@ -549,6 +746,27 @@ def create_assistant(data: AssistantCreate, db: Session = Depends(get_db)):
|
|
|
|
|
tools=data.tools,
|
|
|
|
|
bot_cannot_be_interrupted=data.botCannotBeInterrupted,
|
|
|
|
|
interruption_sensitivity=data.interruptionSensitivity,
|
|
|
|
|
presence_probe_enabled=bool(data.presenceProbeEnabled),
|
|
|
|
|
presence_probe_idle_seconds=_coerce_bounded_float(
|
|
|
|
|
data.presenceProbeIdleSeconds,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_IDLE_SECONDS,
|
|
|
|
|
min_value=PRESENCE_PROBE_MIN_IDLE_SECONDS,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_IDLE_SECONDS,
|
|
|
|
|
),
|
|
|
|
|
presence_probe_cooldown_seconds=_coerce_bounded_float(
|
|
|
|
|
data.presenceProbeCooldownSeconds,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_COOLDOWN_SECONDS,
|
|
|
|
|
min_value=PRESENCE_PROBE_MIN_COOLDOWN_SECONDS,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_COOLDOWN_SECONDS,
|
|
|
|
|
),
|
|
|
|
|
presence_probe_max_prompts=_coerce_bounded_int(
|
|
|
|
|
data.presenceProbeMaxPrompts,
|
|
|
|
|
default_value=PRESENCE_PROBE_DEFAULT_MAX_PROMPTS,
|
|
|
|
|
min_value=1,
|
|
|
|
|
max_value=PRESENCE_PROBE_MAX_PROMPTS_CAP,
|
|
|
|
|
),
|
|
|
|
|
presence_probe_include_context=bool(data.presenceProbeIncludeContext),
|
|
|
|
|
presence_probe_question=str(data.presenceProbeQuestion or "").strip()[:160],
|
|
|
|
|
config_mode=data.configMode,
|
|
|
|
|
api_url=data.apiUrl,
|
|
|
|
|
api_key=data.apiKey,
|
|
|
|
|
@@ -570,6 +788,7 @@ def create_assistant(data: AssistantCreate, db: Session = Depends(get_db)):
|
|
|
|
|
|
|
|
|
|
@router.get("/{id}/opener-audio", response_model=AssistantOpenerAudioOut)
|
|
|
|
|
def get_assistant_opener_audio(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = db.query(Assistant).filter(Assistant.id == id).first()
|
|
|
|
|
if not assistant:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
|
|
|
@@ -578,6 +797,7 @@ def get_assistant_opener_audio(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
|
|
|
|
|
@router.get("/{id}/opener-audio/pcm")
|
|
|
|
|
def get_assistant_opener_audio_pcm(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = db.query(Assistant).filter(Assistant.id == id).first()
|
|
|
|
|
if not assistant:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
|
|
|
@@ -600,6 +820,7 @@ def generate_assistant_opener_audio(
|
|
|
|
|
data: AssistantOpenerAudioGenerateRequest,
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
):
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = db.query(Assistant).filter(Assistant.id == id).first()
|
|
|
|
|
if not assistant:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
|
|
|
@@ -689,6 +910,7 @@ def generate_assistant_opener_audio(
|
|
|
|
|
@router.put("/{id}")
|
|
|
|
|
def update_assistant(id: str, data: AssistantUpdate, db: Session = Depends(get_db)):
|
|
|
|
|
"""更新助手"""
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = db.query(Assistant).filter(Assistant.id == id).first()
|
|
|
|
|
if not assistant:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
|
|
|
@@ -710,6 +932,7 @@ def update_assistant(id: str, data: AssistantUpdate, db: Session = Depends(get_d
|
|
|
|
|
@router.delete("/{id}")
|
|
|
|
|
def delete_assistant(id: str, db: Session = Depends(get_db)):
|
|
|
|
|
"""删除助手"""
|
|
|
|
|
_ensure_assistant_schema(db)
|
|
|
|
|
assistant = db.query(Assistant).filter(Assistant.id == id).first()
|
|
|
|
|
if not assistant:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
|
|
|
|