You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

785 lines
33 KiB
Python

import uuid
import aiohttp
import requests
import time
import asyncio
import json
from datetime import datetime
from fastapi import HTTPException
from sqlalchemy import select, Delete
from src.ircfa_interaction.schema import ChatRequest, ChatResponse, ChatMessage, Performance, ChatHistory, Conversation, ClassifierResponse, ConversationExportUpdate
from src.ircfa_interaction.model import ChatHistory as ChatHistoryModel, Conversation as ConversationModel, LLMConfig, ClassifierConfig, VLLMConfig
from src.rag.service import create as chat_create
from src.database.core import DbSession, CollectorDbSession
from src.database.service import CommonParameters
from src.auth.service import CurrentUser
from src.database.service import search_filter_sort_paginate
from typing import Optional
from langchain_core.prompts import ChatPromptTemplate
from src.rag.lib.prompt_template_list import (
llm_only_prompt,
failure_function_call_prompt,
failure_hari_ini,
default_prompt
)
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_postgres import (
PGVectorStore,
PGEngine,
Column,
PostgresChatMessageHistory,
)
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage, BaseMessage
from langchain_core.runnables.history import RunnableWithMessageHistory
from src.rag.lib.init_vector_table import (
create_vector_table
)
from typing import List, Dict, Optional, Union, Any
from langchain_core.runnables.config import RunnableConfig
from src.ircfa_alert.service import ircfa_alert
from src.equipments.service import get_history_maintainance
async def create(db_session: DbSession, request: ChatRequest, current_user: CurrentUser, location_tag: Optional[str] = None, conversation_id: Optional[str] = None):
"""Stream chat response based on the request and user context.
Yields:
str: Streamed response chunks.
"""
try:
print('Deteksi Intent...', '\nMessage: ', request.message)
intent_result = await ClassifierConfig.chain.ainvoke({"question": request.message})
print(intent_result)
conversation_id = request.conversation_id
if conversation_id is None:
conversation_id = uuid.uuid4()
conversation_title = request.message[:50] + "..." if len(request.message) > 30 else request.message
conversation = ConversationModel(
id=conversation_id,
user_id=current_user.user_id,
conversation_id=conversation_id,
conversation_title=conversation_title,
created_by=current_user.name,
updated_by=current_user.name
)
db_session.add(conversation)
await db_session.commit()
# Determine the response based on intent_result
response_message = None
if (intent_result.intent if hasattr(intent_result, "intent") else None) == "Lainnya":
response_message = (
"Saya adalah AI yang dirancang untuk memberikan informasi dan analisis teknis terkait operasi dan pemeliharaan pembangkit listrik. \n"
"Saya tidak dapat menjawab pertanyaan Anda karena tidak memiliki pengetahuan terkait topik tersebut."
)
elif (
(request.attachments and len(request.attachments) > 0)
or (intent_result.intent if hasattr(intent_result, "intent") else None) == "FileAttachment"
or (intent_result.intent if hasattr(intent_result, "intent") else None) == "Mendeskripsikan"
or (intent_result.intent if hasattr(intent_result, "intent") else None) == "JelaskanGambar"
):
response_message = None
elif (intent_result.confidence < 0.8 and intent_result.equipment_name) or (not intent_result.equipment_name):
response_message = "Pertanyaan kurang dapat dipahami, mohon masukkan pertanyaan lebih spesifik!"
else:
response_message = None # Let the normal flow continue
if response_message is not None:
chat_history = ChatHistoryModel(
id=request.message_id or uuid.uuid4(),
conversation_id=str(conversation_id),
user_id=str(current_user.user_id),
message=request.message,
attachments=[],
attachments_filepath=[],
response=response_message,
context=[],
sources=[],
sources_filepath=[],
faithfullness=None,
answer_relevancy=None,
context_recall=None,
context_relevancy=None,
chat_model=LLMConfig.llm.model,
embedding_model="",
ocr_model="",
message_timestamp=datetime.now(),
response_timestamp=datetime.now(),
time_taken=1,
prompt_tokens=0,
response_tokens=0,
tokens_used=0,
created_by=str(current_user.name),
updated_by=str(current_user.name),
)
db_session.add(chat_history)
await db_session.commit()
yield f"data: {response_message}\n\n"
yield f"event: conversation_id\ndata: {conversation_id}\n\n"
yield f"event: usage_metadata\ndata: {json.dumps({'prompt_tokens': 0, 'response_tokens': 0, 'tokens_used': 0, 'time_taken': 1})}\n\n"
rag_request = ChatRequest(
conversation_id=conversation_id,
message=request.message,
attachments=request.attachments if request.attachments else None,
flow_type=None,
equipment_id=request.equipment_id if request.equipment_id else None,
questions_batch=None
)
print('\nMessage: ', request.message, '\nintent: ', intent_result)
rag_result = chat_create(db_session=db_session, request=rag_request, conversation_id=conversation_id, current_user=current_user, intent_result=intent_result)
response_chunks = []
# If rag_result.message is a generator/stream, yield chunks
if hasattr(rag_result, "__aiter__"):
async for chunk in rag_result:
# print(chunk, end='', flush=True)
text = chunk if isinstance(chunk, str) else str(chunk)
response_chunks.append(text)
yield f"{text}\n\n"
await asyncio.sleep(0.0125)
response_message = ''.join(response_chunks)
else:
response_message = rag_result.message if hasattr(rag_result, "message") else ""
yield f"data: {response_message}\n\n"
# yield f"event: conversation_id\ndata: {conversation_id}\n\n"
# usage_metadata = {
# "prompt_tokens": rag_result.prompt_tokens if hasattr(rag_result, "prompt_tokens") else 0,
# "response_tokens": rag_result.response_tokens if hasattr(rag_result, "response_tokens") else 0,
# "tokens_used": rag_result.tokens_used if hasattr(rag_result, "tokens_used") else 0,
# "time_taken": rag_result.time_taken if hasattr(rag_result, "time_taken") else 0
# }
# yield f"event: usage_metadata\ndata: {json.dumps(usage_metadata)}\n\n"
# yield f"event: context\ndata: {json.dumps(rag_result.context if hasattr(rag_result, 'context') else [])}\n\n"
except Exception as e:
print("Gagal mendapatkan jawaban LLM:", e)
yield f"event: error\ndata: {str(e)}\n\n"
# Optionally, raise HTTPException if you want to propagate error
# raise HTTPException(status_code=500, detail=f"Failed to continue conversation: {str(e)}")
async def get_conversation(
*,
db_session: DbSession,
current_user: CurrentUser,
search: Optional[str] = None,
page: Optional[int] = 1,
items_per_page: Optional[int] = 50,
is_exported: Optional[bool] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
common
) -> list[Conversation]:
"""
Get all conversations for the current user.
"""
try:
query = select(ConversationModel).where(ConversationModel.user_id == current_user.user_id).order_by(ConversationModel.updated_at.desc())
if search:
query = query.filter(ConversationModel.conversation_title.ilike(f"%{search}%"))
if is_exported is not None:
query = query.filter(ConversationModel.is_exported == is_exported)
if start_date:
try:
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
query = query.filter(ConversationModel.updated_at >= start_dt)
except (ValueError, TypeError):
pass
if end_date:
try:
# Set to end of day to be inclusive
end_dt = datetime.strptime(end_date, "%Y-%m-%d").replace(hour=23, minute=59, second=59, microsecond=999999)
query = query.filter(ConversationModel.updated_at <= end_dt)
except (ValueError, TypeError):
pass
common["items_per_page"] = items_per_page
common["page"] = page
results = await search_filter_sort_paginate(model=query, **common)
return results
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get conversations: {str(e)}")
async def get_chat_by_id(db_session: DbSession, chat_history_id: str, conversation_id: str, current_user: CurrentUser) -> ChatHistory:
"""
Get a chat by conversation ID for the current user.
"""
try:
chat = await db_session.execute(
select(ChatHistoryModel)
.where(ChatHistoryModel.conversation_id == conversation_id)
.where(ChatHistoryModel.user_id == current_user.user_id)
)
return chat.scalar()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get chat: {str(e)}")
async def get_conversation_by_id(db_session: DbSession, conversation_id: str, current_user: CurrentUser) -> Conversation | None:
"""
Get a conversation by ID for the current user.
"""
try:
conversation = await db_session.execute(
select(ConversationModel)
.where(ConversationModel.conversation_id == conversation_id)
.where(ConversationModel.user_id == current_user.user_id)
)
return conversation.scalar_one_or_none()
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get conversation: {str(e)}")
async def get_conversation_message(db_session: DbSession, conversation_id: str, current_user: CurrentUser) -> list[ChatHistory]:
"""
Get chat history for a specific conversation.
"""
try:
chat_history = await db_session.execute(
select(ChatHistoryModel)
.where(ChatHistoryModel.conversation_id == conversation_id)
.where(ChatHistoryModel.user_id == current_user.user_id)
.order_by(ChatHistoryModel.created_at.asc())
)
chat_history_records = chat_history.scalars().all()
return [ChatHistory.model_validate(record.__dict__) for record in chat_history_records]
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get conversation history: {str(e)}")
async def continue_conversation(db_session: DbSession, conversation_id: str, request: ChatRequest, current_user: CurrentUser) -> ChatResponse:
"""
Continue an existing conversation with a new message.
"""
try:
# Get existing conversation history
chat_records = await get_conversation_message(db_session, conversation_id, current_user)
# Build conversation history
conversation_history = []
for record in chat_records:
conversation_history.append(ChatMessage(role="user", content=record.message))
conversation_history.append(ChatMessage(role="assistant", content=record.response))
# Create request with complete history
updated_request = ChatRequest(
message=request.message,
conversationHistory=conversation_history
)
print('Deteksi Intent...', '\nMessage: ', request.message)
intent_result = await ClassifierConfig.chain.ainvoke(updated_request)
rag_request = ChatRequestRAG(
conversation_id=None,
message=request.message,
conversation_history=[],
attachments=None,
flow_type=None,
equipment_id=None,
questions_batch=None
)
rag_result = await chat_create(db_session=db_session, request=rag_request, current_user=current_user)
if len(rag_result.context) == 0:
print('Melakukan FT...', '\nMessage: ', request.message, '\nintent: ', intent_result)
ft_result = await LLMConfig.chain.ainvoke({"question": request.message})
response = ft_result.content
else:
print('Melakukan RAG...', '\nMessage: ', request.message, '\nintent: ', intent_result)
response = rag_result.message
# Update conversation history
updated_history = conversation_history.copy()
updated_history.append(ChatMessage(role="user", content=request.message))
updated_history.append(ChatMessage(role="assistant", content=response))
chat_history = ChatHistory(
user_id=current_user.user_id,
conversation_id=conversation_id,
message=request.message,
response=response,
created_by=current_user.name,
updated_by=current_user.name
)
db_session.add(chat_history)
await db_session.commit()
return ChatResponseRAG(
message=response,
userMessage=request.message,
conversationHistory=updated_history,
timestamp=datetime.now().isoformat(),
model=LLMConfig.llm.model,
time_taken=rag_result.time_taken,
prompt_tokens= rag_result.prompt_tokens,
response_tokens= rag_result.response_tokens,
tokens_used= rag_result.tokens_used
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to continue conversation: {str(e)}")
async def update_conversation_title(db_session: DbSession, conversation_id: str, title: str, current_user: CurrentUser) -> Conversation:
"""Updates covnersation title."""
new_title = title
query = select(ConversationModel).where(
ConversationModel.conversation_id == conversation_id,
ConversationModel.user_id == current_user.user_id
)
result = await db_session.execute(query)
conversation = result.scalars().one_or_none()
if not conversation:
raise HTTPException(status_code=404, detail="Conversation not found")
if len(new_title) > 30:
new_title = new_title[:30] + "..."
conversation.conversation_title = new_title
conversation.updated_by = current_user.name
await db_session.commit()
return conversation
async def update_chat_history_feedback(
db_session: DbSession,
chat_history_id: str,
feedback: str,
current_user: CurrentUser,
comment: Optional[str] = None,
screenshot: Optional[str] = None
):
"""
Update feedback (rating + optional comment + optional screenshot) for a chat history entry.
"""
try:
chat_history = await db_session.execute(
select(ChatHistoryModel)
.where(ChatHistoryModel.id == chat_history_id)
.where(ChatHistoryModel.user_id == current_user.user_id)
)
chat_history = chat_history.scalar_one_or_none()
if not chat_history:
raise HTTPException(status_code=404, detail="Chat history not found")
chat_history.feedback = feedback
chat_history.feedback_comment = comment
chat_history.feedback_screenshot = screenshot
chat_history.updated_by = current_user.name
db_session.add(chat_history)
await db_session.commit()
response = ChatHistory(
id = str(chat_history.id),
user_id = str(chat_history.user_id),
message = str(chat_history.message),
attachments = chat_history.attachments if chat_history.attachments else None,
attachments_filepath = chat_history.attachments_filepath if chat_history.attachments_filepath else None,
response = str(chat_history.response),
context = chat_history.context if chat_history.context else None,
sources = chat_history.sources if chat_history.sources else None,
sources_filepath = chat_history.sources_filepath if chat_history.sources_filepath else None,
time_taken = chat_history.time_taken if chat_history.time_taken else None,
prompt_tokens = chat_history.prompt_tokens if chat_history.prompt_tokens else None,
response_tokens = chat_history.response_tokens if chat_history.response_tokens else None,
tokens_used = chat_history.tokens_used if chat_history.tokens_used else None,
timestamp = chat_history.message_timestamp if chat_history.message_timestamp else None,
response_timestamp = chat_history.response_timestamp if chat_history.response_timestamp else None,
conversation_id = str(chat_history.conversation_id),
chat_model = chat_history.chat_model if chat_history.chat_model else None,
embedding_model = chat_history.embedding_model if chat_history.embedding_model else None,
ocr_model = chat_history.ocr_model if chat_history.ocr_model else None,
feedback = chat_history.feedback if chat_history.feedback else None,
feedback_comment = chat_history.feedback_comment if chat_history.feedback_comment else None,
feedback_screenshot = chat_history.feedback_screenshot if chat_history.feedback_screenshot else None,
updated_at = chat_history.updated_at if chat_history.updated_at else None,
created_at = chat_history.created_at if chat_history.created_at else None,
)
return response
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to update chat history feedback: {str(e)}")
async def get_feedback_analytics(
db_session: DbSession,
current_user: CurrentUser,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
):
"""
Aggregate feedback analytics across all entries for the evaluation dashboard.
Admins see all users' feedback; regular users see only their own.
"""
try:
is_admin = getattr(current_user, "role", None) in ("Admin", "Super Admin", "Application Administrator")
query = select(ChatHistoryModel).where(ChatHistoryModel.feedback.isnot(None))
if not is_admin:
query = query.where(ChatHistoryModel.user_id == current_user.user_id)
if start_date:
try:
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
query = query.where(ChatHistoryModel.response_timestamp >= start_dt)
except (ValueError, TypeError):
pass
if end_date:
try:
end_dt = datetime.strptime(end_date, "%Y-%m-%d").replace(hour=23, minute=59, second=59)
query = query.where(ChatHistoryModel.response_timestamp <= end_dt)
except (ValueError, TypeError):
pass
result = await db_session.execute(query)
records = result.scalars().all()
good_count = sum(1 for r in records if r.feedback == "good")
bad_count = sum(1 for r in records if r.feedback == "bad")
with_comments = sum(1 for r in records if r.feedback_comment)
with_screenshots = sum(1 for r in records if r.feedback_screenshot)
total = len(records)
# Group by day
day_map: dict = {}
for r in records:
if not r.response_timestamp:
continue
day = r.response_timestamp.strftime("%Y-%m-%d")
if day not in day_map:
day_map[day] = {"date": day, "good": 0, "bad": 0}
if r.feedback == "good":
day_map[day]["good"] += 1
elif r.feedback == "bad":
day_map[day]["bad"] += 1
by_day = sorted(day_map.values(), key=lambda x: x["date"])
# Group by model
model_map: dict = {}
for r in records:
m = r.chat_model or "Unknown"
if m not in model_map:
model_map[m] = {"model": m, "good": 0, "bad": 0, "total": 0}
model_map[m]["total"] += 1
if r.feedback == "good":
model_map[m]["good"] += 1
elif r.feedback == "bad":
model_map[m]["bad"] += 1
by_model = sorted(model_map.values(), key=lambda x: x["total"], reverse=True)
# Recent feedback entries (paginated-like, last 50)
recent = sorted(records, key=lambda x: x.response_timestamp or datetime.min, reverse=True)[:50]
recent_feedback = [
{
"id": str(r.id),
"conversation_id": str(r.conversation_id),
"feedback": r.feedback,
"feedback_comment": r.feedback_comment,
"has_screenshot": bool(r.feedback_screenshot),
"screenshot": r.feedback_screenshot,
"message_snippet": (r.message[:120] + "...") if r.message and len(r.message) > 120 else r.message,
"response_snippet": (r.response[:180] + "...") if r.response and len(r.response) > 180 else r.response,
"timestamp": r.response_timestamp.isoformat() if r.response_timestamp else None,
"chat_model": r.chat_model,
"user_id": str(r.user_id),
}
for r in recent
]
return {
"summary": {
"total_feedback": total,
"good_count": good_count,
"bad_count": bad_count,
"with_comments": with_comments,
"with_screenshots": with_screenshots,
"good_rate": round(good_count / total * 100, 1) if total > 0 else 0,
},
"by_day": by_day,
"by_model": by_model,
"recent_feedback": recent_feedback,
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get feedback analytics: {str(e)}")
async def delete_conversation(db_session: DbSession, conversation_id: str, current_user: CurrentUser) -> dict:
"""
Cascade delete a conversation and its chat history for the current user.
"""
try:
# Delete chat history records first (cascade)
# await db_session.execute(
# ChatHistory.__table__.delete().where(
# (ChatHistory.conversation_id == conversation_id) &
# (ChatHistory.user_id == current_user.user_id)
# )
# )
# await db_session.execute(
# ChatHistory.__table__.delete().where(
# (ChatHistory.conversation_id == conversation_id) &
# (ChatHistory.user_id == current_user.user_id)
# )
# )
# Delete conversation record
query = Delete(ConversationModel).where(ConversationModel.conversation_id == conversation_id)
await db_session.execute(query)
await db_session.commit()
return {"detail": "Conversation and related chat history deleted successfully."}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to cascade delete conversation: {str(e)}")
async def delete_chat_by_id(db_session: DbSession, chat_history_id: str, conversation_id: str, current_user: CurrentUser) -> dict:
"""
Delete a specific chat history entry by its ID for the current user.
"""
try:
query = Delete(ChatHistoryModel).where(
(ChatHistoryModel.id == chat_history_id) &
(ChatHistoryModel.conversation_id == conversation_id) &
(ChatHistoryModel.user_id == current_user.user_id)
)
result = await db_session.execute(query)
await db_session.commit()
if result.rowcount == 0:
raise HTTPException(status_code=404, detail="Chat history not found or not authorized to delete.")
return {
"chat_history_id": chat_history_id,
"conversation_id": conversation_id
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to delete chat history: {str(e)}")
async def update_conversation_export_status(db_session: DbSession, conversation_id: str, update_data: ConversationExportUpdate, current_user: CurrentUser) -> ConversationModel:
"""Updates conversation export status."""
query = select(ConversationModel).where(
ConversationModel.conversation_id == conversation_id,
ConversationModel.user_id == current_user.user_id
)
result = await db_session.execute(query)
conversation = result.scalars().one_or_none()
if not conversation:
raise HTTPException(status_code=404, detail="Conversation not found")
conversation.is_exported = update_data.is_exported
conversation.report_name = update_data.report_name
conversation.report_content = update_data.report_content
conversation.exported_at = datetime.now()
conversation.updated_by = current_user.name
await db_session.commit()
await db_session.refresh(conversation)
return conversation
async def init_db_models(db_session):
"""Populates the rcfa_model_config table on startup if it's empty."""
from src.ircfa_interaction.model import ModelConfig
from src.ircfa_interaction.config import MODELS_DATA as STATIC_MODELS_DATA, DEFAULT_LLM_CONFIG as STATIC_DEFAULT_LLM_CONFIG
# Check if table already has entries
result = await db_session.execute(select(ModelConfig))
existing = result.scalars().all()
if existing:
# Table already initialized. Just reload in-memory cache
await refresh_in_memory_configs(db_session)
return
print("Initializing rcfa_model_config table with default models...")
for m in STATIC_MODELS_DATA:
db_model = ModelConfig(
name=m.name,
display_name=m.display_name,
model=m.model,
model_server=m.model_server,
api_key=m.api_key,
provider=m.provider,
description=m.description,
stream=m.stream,
enabled=m.enabled,
is_default=(m.name == STATIC_DEFAULT_LLM_CONFIG),
generate_cfg=m.generate_cfg,
created_by="system",
updated_by="system"
)
db_session.add(db_model)
await db_session.commit()
# Sync memory cache
await refresh_in_memory_configs(db_session)
async def refresh_in_memory_configs(db_session):
"""Queries configurations from the database and refreshes the in-memory cache."""
from src.ircfa_interaction.model import ModelConfig
from src.ircfa_interaction.config import LLMModelConfig, update_models_config
result = await db_session.execute(select(ModelConfig).order_by(ModelConfig.name))
db_models = result.scalars().all()
if not db_models:
return
models_list = []
default_name = "icon-qwen"
for m in db_models:
cfg = LLMModelConfig(
name=m.name,
display_name=m.display_name,
model=m.model,
model_server=m.model_server,
api_key=m.api_key,
provider=m.provider,
description=m.description,
stream=m.stream,
enabled=m.enabled,
generate_cfg=m.generate_cfg or {
"temperature": 0.3,
"top_p": 0.9,
"max_tokens": 16382 * 2
}
)
models_list.append(cfg)
if m.is_default:
default_name = m.name
# Update in-memory lists in config.py
update_models_config(models_list, default_name)
async def toggle_model_service(db_session, name: str, enabled: bool, current_user: CurrentUser):
"""Toggles enablement status of a model config in the database and updates memory cache."""
from src.ircfa_interaction.model import ModelConfig
result = await db_session.execute(select(ModelConfig).where(ModelConfig.name == name))
model = result.scalar_one_or_none()
if not model:
raise HTTPException(status_code=404, detail="Model not found")
model.enabled = enabled
model.updated_by = current_user.name
await db_session.commit()
await refresh_in_memory_configs(db_session)
return model
async def set_default_model_service(db_session, name: str, current_user: CurrentUser):
"""Selects a model as the default config in the database and updates memory cache."""
from src.ircfa_interaction.model import ModelConfig
from sqlalchemy import update
result = await db_session.execute(select(ModelConfig).where(ModelConfig.name == name))
model = result.scalar_one_or_none()
if not model:
raise HTTPException(status_code=404, detail="Model not found")
# Unset all defaults
await db_session.execute(
update(ModelConfig).values(is_default=False)
)
model.is_default = True
model.updated_by = current_user.name
await db_session.commit()
await refresh_in_memory_configs(db_session)
return model
async def get_all_models_service(db_session):
"""Retrieves all model configurations from the database."""
from src.ircfa_interaction.model import ModelConfig
result = await db_session.execute(select(ModelConfig).order_by(ModelConfig.name))
return result.scalars().all()
async def create_model_service(db_session, data, current_user: CurrentUser):
"""Creates a new model configuration in the database and refreshes memory cache."""
from src.ircfa_interaction.model import ModelConfig
# Check if name already exists
result = await db_session.execute(select(ModelConfig).where(ModelConfig.name == data.name))
if result.scalar_one_or_none():
raise HTTPException(status_code=400, detail=f"Model config with name '{data.name}' already exists.")
model = ModelConfig(
name=data.name,
display_name=data.display_name,
model=data.model,
model_server=data.model_server,
api_key=data.api_key or "not-needed",
provider=data.provider or "openai",
description=data.description,
stream=data.stream if data.stream is not None else True,
enabled=data.enabled if data.enabled is not None else True,
is_default=False,
generate_cfg=data.generate_cfg,
created_by=current_user.name,
updated_by=current_user.name
)
db_session.add(model)
await db_session.commit()
await refresh_in_memory_configs(db_session)
return model
async def update_model_service(db_session, name: str, data, current_user: CurrentUser):
"""Updates an existing model configuration in the database and refreshes memory cache."""
from src.ircfa_interaction.model import ModelConfig
result = await db_session.execute(select(ModelConfig).where(ModelConfig.name == name))
model = result.scalar_one_or_none()
if not model:
raise HTTPException(status_code=404, detail="Model not found")
model.display_name = data.display_name
model.model = data.model
model.model_server = data.model_server
model.api_key = data.api_key if data.api_key is not None else model.api_key
model.provider = data.provider if data.provider is not None else model.provider
model.description = data.description
model.stream = data.stream if data.stream is not None else model.stream
model.enabled = data.enabled if data.enabled is not None else model.enabled
if data.generate_cfg is not None:
model.generate_cfg = data.generate_cfg
model.updated_by = current_user.name
await db_session.commit()
await refresh_in_memory_configs(db_session)
return model
async def delete_model_service(db_session, name: str, current_user: CurrentUser):
"""Deletes a model configuration from the database and refreshes memory cache."""
from src.ircfa_interaction.model import ModelConfig
result = await db_session.execute(select(ModelConfig).where(ModelConfig.name == name))
model = result.scalar_one_or_none()
if not model:
raise HTTPException(status_code=404, detail="Model not found")
if model.is_default:
raise HTTPException(status_code=400, detail="Cannot delete the default model config. Please set another model as default first.")
await db_session.delete(model)
await db_session.commit()
await refresh_in_memory_configs(db_session)
return {"detail": f"Model config '{name}' deleted successfully."}