pablonyx 11da0d9889
Add user specific chat session temperature (#3867)
* add user specific chat session temperature

* kbetter typing

* update
2025-02-01 17:29:58 -08:00

260 lines
8.2 KiB
Python

from datetime import datetime
from typing import Any
from typing import TYPE_CHECKING
from uuid import UUID
from pydantic import BaseModel
from pydantic import model_validator
from onyx.chat.models import PersonaOverrideConfig
from onyx.chat.models import RetrievalDocs
from onyx.configs.constants import DocumentSource
from onyx.configs.constants import MessageType
from onyx.configs.constants import SearchFeedbackType
from onyx.configs.constants import SessionType
from onyx.context.search.models import BaseFilters
from onyx.context.search.models import ChunkContext
from onyx.context.search.models import RerankingDetails
from onyx.context.search.models import RetrievalDetails
from onyx.context.search.models import SearchDoc
from onyx.context.search.models import Tag
from onyx.db.enums import ChatSessionSharedStatus
from onyx.file_store.models import FileDescriptor
from onyx.llm.override_models import LLMOverride
from onyx.llm.override_models import PromptOverride
from onyx.tools.models import ToolCallFinalResult
if TYPE_CHECKING:
pass
class SourceTag(Tag):
source: DocumentSource
class TagResponse(BaseModel):
tags: list[SourceTag]
class UpdateChatSessionThreadRequest(BaseModel):
# If not specified, use Onyx default persona
chat_session_id: UUID
new_alternate_model: str
class UpdateChatSessionTemperatureRequest(BaseModel):
chat_session_id: UUID
temperature_override: float
class ChatSessionCreationRequest(BaseModel):
# If not specified, use Onyx default persona
persona_id: int = 0
description: str | None = None
class CreateChatSessionID(BaseModel):
chat_session_id: UUID
class ChatFeedbackRequest(BaseModel):
chat_message_id: int
is_positive: bool | None = None
feedback_text: str | None = None
predefined_feedback: str | None = None
@model_validator(mode="after")
def check_is_positive_or_feedback_text(self) -> "ChatFeedbackRequest":
if self.is_positive is None and self.feedback_text is None:
raise ValueError("Empty feedback received.")
return self
"""
Currently the different branches are generated by changing the search query
[Empty Root Message] This allows the first message to be branched as well
/ | \
[First Message] [First Message Edit 1] [First Message Edit 2]
| |
[Second Message] [Second Message of Edit 1 Branch]
"""
class CreateChatMessageRequest(ChunkContext):
"""Before creating messages, be sure to create a chat_session and get an id"""
chat_session_id: UUID
# This is the primary-key (unique identifier) for the previous message of the tree
parent_message_id: int | None
# New message contents
message: str
# Files that we should attach to this message
file_descriptors: list[FileDescriptor]
# If no prompt provided, uses the largest prompt of the chat session
# but really this should be explicitly specified, only in the simplified APIs is this inferred
# Use prompt_id 0 to use the system default prompt which is Answer-Question
prompt_id: int | None
# If search_doc_ids provided, then retrieval options are unused
search_doc_ids: list[int] | None
retrieval_options: RetrievalDetails | None
# Useable via the APIs but not recommended for most flows
rerank_settings: RerankingDetails | None = None
# allows the caller to specify the exact search query they want to use
# will disable Query Rewording if specified
query_override: str | None = None
# enables additional handling to ensure that we regenerate with a given user message ID
regenerate: bool | None = None
# allows the caller to override the Persona / Prompt
# these do not persist in the chat thread details
llm_override: LLMOverride | None = None
prompt_override: PromptOverride | None = None
# Allows the caller to override the temperature for the chat session
# this does persist in the chat thread details
temperature_override: float | None = None
# allow user to specify an alternate assistnat
alternate_assistant_id: int | None = None
# This takes the priority over the prompt_override
# This won't be a type that's passed in directly from the API
persona_override_config: PersonaOverrideConfig | None = None
# used for seeded chats to kick off the generation of an AI answer
use_existing_user_message: bool = False
# used for "OpenAI Assistants API"
existing_assistant_message_id: int | None = None
# forces the LLM to return a structured response, see
# https://platform.openai.com/docs/guides/structured-outputs/introduction
structured_response_format: dict | None = None
@model_validator(mode="after")
def check_search_doc_ids_or_retrieval_options(self) -> "CreateChatMessageRequest":
if self.search_doc_ids is None and self.retrieval_options is None:
raise ValueError(
"Either search_doc_ids or retrieval_options must be provided, but not both or neither."
)
return self
def model_dump(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
data = super().model_dump(*args, **kwargs)
data["chat_session_id"] = str(data["chat_session_id"])
return data
class ChatMessageIdentifier(BaseModel):
message_id: int
class ChatRenameRequest(BaseModel):
chat_session_id: UUID
name: str | None = None
class ChatSessionUpdateRequest(BaseModel):
sharing_status: ChatSessionSharedStatus
class DeleteAllSessionsRequest(BaseModel):
session_type: SessionType
class RenameChatSessionResponse(BaseModel):
new_name: str # This is only really useful if the name is generated
class ChatSessionDetails(BaseModel):
id: UUID
name: str | None
persona_id: int | None = None
time_created: str
shared_status: ChatSessionSharedStatus
folder_id: int | None = None
current_alternate_model: str | None = None
current_temperature_override: float | None = None
class ChatSessionsResponse(BaseModel):
sessions: list[ChatSessionDetails]
class SearchFeedbackRequest(BaseModel):
message_id: int
document_id: str
document_rank: int
click: bool
search_feedback: SearchFeedbackType | None = None
@model_validator(mode="after")
def check_click_or_search_feedback(self) -> "SearchFeedbackRequest":
click, feedback = self.click, self.search_feedback
if click is False and feedback is None:
raise ValueError("Empty feedback received.")
return self
class ChatMessageDetail(BaseModel):
message_id: int
parent_message: int | None = None
latest_child_message: int | None = None
message: str
rephrased_query: str | None = None
context_docs: RetrievalDocs | None = None
message_type: MessageType
time_sent: datetime
overridden_model: str | None
alternate_assistant_id: int | None = None
# Dict mapping citation number to db_doc_id
chat_session_id: UUID | None = None
citations: dict[int, int] | None = None
files: list[FileDescriptor]
tool_call: ToolCallFinalResult | None
def model_dump(self, *args: list, **kwargs: dict[str, Any]) -> dict[str, Any]: # type: ignore
initial_dict = super().model_dump(mode="json", *args, **kwargs) # type: ignore
initial_dict["time_sent"] = self.time_sent.isoformat()
return initial_dict
class SearchSessionDetailResponse(BaseModel):
search_session_id: UUID
description: str | None
documents: list[SearchDoc]
messages: list[ChatMessageDetail]
class ChatSessionDetailResponse(BaseModel):
chat_session_id: UUID
description: str | None
persona_id: int | None = None
persona_name: str | None
persona_icon_color: str | None
persona_icon_shape: int | None
messages: list[ChatMessageDetail]
time_created: datetime
shared_status: ChatSessionSharedStatus
current_alternate_model: str | None
current_temperature_override: float | None
# This one is not used anymore
class QueryValidationResponse(BaseModel):
reasoning: str
answerable: bool
class AdminSearchRequest(BaseModel):
query: str
filters: BaseFilters
class AdminSearchResponse(BaseModel):
documents: list[SearchDoc]