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
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."}
|
|
|
|
|
|
|