mirror of
https://github.com/mindverse/Second-Me.git
synced 2026-09-06 09:56:22 +00:00
* Enhance GGUF model handling with timestamps, metadata and memory training status * Check if is_trained exists * fix * cloud service * Change the data type of the is_trained field to boolean and update the related logic to reflect this change * Change the data type of the is_trained field to boolean and update the related logic to reflect this change * Add gguf path to json file * Added model selection function, updated model list acquisition logic, and enhanced model information display * Update the model service startup logic, add integrity check for the model path, and support obtaining the model path from different fields * Service Change * full cloud service * feat: implement async cloud training process with job tracking and API key management * Progress bar modification * feat: Add Local and Cloud Training Configuration Components - Introduced LocalTrainingConfig component for configuring local training parameters. - Updated TrainingConfiguration component to include tabs for Local and Cloud training configurations. - Added API functions for setting and getting cloud service API keys. - Created useCloudProviderStore for managing cloud provider configurations. - Enhanced event utility to include a new event for showing cloud provider modal. * Refactor cloud provider and training configuration components - Updated CloudProviderModal to handle cloud service API key management. - Replaced API key handling with model configuration updates in CloudProviderModal. - Enhanced CloudTrainingConfig to manage cloud models based on API key availability. - Introduced new cloud service functions for listing available models and managing training jobs. - Modified LocalTrainingConfig to ensure default model selection and synchronization. - Updated TrainingConfiguration to manage model switching between local and cloud environments. - Refactored useCloudProviderStore to integrate cloud service API key handling. - Adjusted useTrainingStore to prioritize model name selection based on the active environment. * Stream Output * feat: Enhance training configuration and progress components - Updated LocalTrainingConfig to improve default model handling and avoid unnecessary updates. - Introduced LocalTrainingProgress component to manage local training progress display. - Refactored TrainingConfiguration to support both local and cloud training types, including updated button text and actions. - Modified TrainingProgress to conditionally render local or cloud training progress based on the selected training type. - Added cloud service functions for starting training and managing job information. - Adjusted training parameter interfaces to ensure consistency across local and cloud models. * Stream response change * feat: Enhance cloud training and inference capabilities - Updated TrainingProgress component to handle cloud training progress data and job ID. - Modified trainExposureModel to allow nullable path and added optional stageName. - Enhanced useSSE hook to support cloud model inference with new parameters. - Introduced CloudProgressData type to align cloud training progress with local training structure. - Implemented cloud inference request handling with local knowledge retrieval in cloudService. - Added utility functions for managing active cloud model state in cloudModelUtils. - Updated cloud inference endpoint to support local knowledge retrieval before cloud inference. - Refactored advanced chat service to utilize new message structure for cloud inference. - Enhanced prompt strategies to incorporate knowledge retrieval based on user messages. * feat: Delete the training parameter debugging information component * Resume training at breakpoint * Repair data redundancy * Stop system modification * fix error: reset training * fix stop and reset * Change chat reply format * Enhance cloud and local service management with status tracking and improved progress reporting - Implemented service status file management in cloud and local services to track active status and model information. - Added endpoints to start and stop cloud services, including validation for existing services. - Enhanced local service management with status checks and progress updates during document processing and chunk embedding. - Introduced real-time progress tracking for document embedding and chunk processing, allowing for incremental updates. - Improved error handling and logging throughout the service management processes. - Refactored chat request handling to intelligently route between local and cloud services based on current status. * feat:Cleaned up code comment * translate Chinese comments to English in cloud service modules * translate into chinese * feat: Enhance cloud provider configuration and training management with API key handling and tab switching logic * bug fix * Add is_trained field modification in the cloud * feat: Refactor training parameters management to separate local and cloud configurations * feat: Update training parameter types to improve type safety and consistency * feat: Add data synthesis mode to cloud training parameters and update related components * feat: The document embedding part is restored to its original state * refactor: optimize cloud training process with improved stop handling and file path updates * feat: Update the default values and merging logic of cloud training parameters to ensure parameter consistency * feat: Add API key preloading function to optimize the loading experience when the modal box is opened * feat: Optimize CloudProviderModal component, add API key preloading and state management * fix: Simplify cloud provider display by removing conditional rendering for Alibaba Cloud * feat: Update .gitignore to include job_id.json and add .gitkeep for gguf directory --------- Co-authored-by: wyx-hhhh <1360479992@qq.com>
356 lines
14 KiB
Python
356 lines
14 KiB
Python
"""
|
|
Chat service for handling different types of chat interactions
|
|
"""
|
|
import logging
|
|
from typing import Optional, List, Dict, Any, Union, Iterator, Type
|
|
import uuid
|
|
from typing import Tuple
|
|
from datetime import datetime
|
|
|
|
from lpm_kernel.api.services.user_llm_config_service import UserLLMConfigService
|
|
from lpm_kernel.api.domains.kernel2.dto.chat_dto import ChatRequest
|
|
from lpm_kernel.api.services.local_llm_service import local_llm_service
|
|
from lpm_kernel.api.domains.kernel2.services.message_builder import MultiTurnMessageBuilder
|
|
from lpm_kernel.api.domains.kernel2.services.prompt_builder import (
|
|
SystemPromptStrategy,
|
|
BasePromptStrategy,
|
|
RoleBasedStrategy,
|
|
KnowledgeEnhancedStrategy,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ChatService:
|
|
"""Chat service for handling different types of chat interactions"""
|
|
|
|
def __init__(self):
|
|
"""Initialize chat service"""
|
|
# Base strategy chain, must contain at least one base strategy
|
|
self.default_strategy_chain = [BasePromptStrategy, RoleBasedStrategy]
|
|
|
|
def _get_strategy_chain(
|
|
self,
|
|
request: ChatRequest,
|
|
strategy_chain: Optional[List[Type[SystemPromptStrategy]]] = None,
|
|
) -> List[Type[SystemPromptStrategy]]:
|
|
"""
|
|
Get the strategy chain to use for message building
|
|
|
|
Args:
|
|
request: Chat request containing message and other parameters
|
|
strategy_chain: Optional list of strategy classes to use
|
|
|
|
Returns:
|
|
List of strategy classes to use
|
|
|
|
Raises:
|
|
ValueError: If strategy_chain is empty or None and no default chain is available
|
|
"""
|
|
# If custom strategy chain is provided, validate and return
|
|
if strategy_chain is not None:
|
|
if not strategy_chain:
|
|
raise ValueError("Strategy chain cannot be empty")
|
|
if not any(issubclass(s, BasePromptStrategy) for s in strategy_chain):
|
|
raise ValueError("Strategy chain must contain at least one base strategy")
|
|
return strategy_chain
|
|
|
|
# Use default strategy chain
|
|
result_chain = self.default_strategy_chain.copy()
|
|
|
|
# Add knowledge enhancement strategy based on request parameters
|
|
enable_l0 = request.metadata.get('enable_l0_retrieval', False) if request.metadata else False
|
|
enable_l1 = request.metadata.get('enable_l1_retrieval', False) if request.metadata else False
|
|
if enable_l0 or enable_l1:
|
|
result_chain.append(KnowledgeEnhancedStrategy)
|
|
|
|
return result_chain
|
|
|
|
def _build_messages(
|
|
self,
|
|
request: ChatRequest,
|
|
strategy_chain: Optional[List[Type[SystemPromptStrategy]]] = None,
|
|
) -> List[Dict[str, str]]:
|
|
"""
|
|
Build messages using the specified strategy chain
|
|
|
|
Args:
|
|
request: Chat request containing message and other parameters
|
|
strategy_chain: Optional list of strategy classes to use. If None, uses default chain
|
|
|
|
Returns:
|
|
List of message dictionaries
|
|
"""
|
|
# Get and validate strategy chain
|
|
final_strategy_chain = self._get_strategy_chain(request, strategy_chain)
|
|
|
|
# Build messages
|
|
message_builder = MultiTurnMessageBuilder(request, strategy_chain=final_strategy_chain)
|
|
messages = message_builder.build_messages()
|
|
|
|
# Log debug information
|
|
logger.info("Using strategy chain: %s", [s.__name__ for s in final_strategy_chain])
|
|
logger.info("Final messages for LLM:")
|
|
for msg in messages:
|
|
logger.info(f"Role: {msg['role']}, Content: {msg['content']}")
|
|
|
|
return messages
|
|
|
|
def _process_chat_response(self, chunk, full_response: Optional[Any], full_content: str) -> Tuple[Any, str, Optional[str]]:
|
|
"""
|
|
Process custom chat_response format data
|
|
|
|
Args:
|
|
chunk: Response data chunk
|
|
full_response: Current complete response object
|
|
full_content: Current accumulated content
|
|
|
|
Returns:
|
|
Tuple[Any, str, Optional[str]]: (Updated response object, Updated content, Finish reason)
|
|
"""
|
|
finish_reason = None
|
|
logger.info(f"Processing custom format response: {chunk}")
|
|
|
|
# Get content
|
|
content = ""
|
|
if isinstance(chunk, dict):
|
|
content = chunk.get("content", "")
|
|
is_done = chunk.get("done", False)
|
|
else:
|
|
content = chunk.content if hasattr(chunk, 'content') else ""
|
|
is_done = chunk.done if hasattr(chunk, 'done') else False
|
|
|
|
if content:
|
|
full_content += content
|
|
logger.info(f"Added content from custom format, current length: {len(full_content)}")
|
|
|
|
# Initialize response object (if needed)
|
|
if not full_response:
|
|
full_response = {
|
|
"id": str(uuid.uuid4()),
|
|
"object": "chat.completion.chunk",
|
|
"created": int(datetime.now().timestamp()),
|
|
"model": "models/lpm",
|
|
"system_fingerprint": None,
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"delta": {
|
|
"content": ""
|
|
},
|
|
"finish_reason": None
|
|
}
|
|
]
|
|
}
|
|
|
|
# Check if completed
|
|
if is_done:
|
|
finish_reason = 'stop'
|
|
logger.info("Got finish_reason from custom format: stop")
|
|
|
|
return full_response, full_content, finish_reason
|
|
|
|
def _process_openai_response(self, chunk, full_response: Optional[Any], full_content: str) -> Tuple[Any, str, Optional[str]]:
|
|
"""
|
|
Process OpenAI format response data
|
|
|
|
Args:
|
|
chunk: Response data chunk
|
|
full_response: Current complete response object
|
|
full_content: Current accumulated content
|
|
|
|
Returns:
|
|
Tuple[Any, str, Optional[str]]: (Updated response object, Updated content, Finish reason)
|
|
"""
|
|
finish_reason = None
|
|
|
|
if not hasattr(chunk, 'choices'):
|
|
logger.warning(f"Chunk has no choices attribute: {chunk}")
|
|
return full_response, full_content, finish_reason
|
|
|
|
choices = getattr(chunk, 'choices', None)
|
|
if not choices:
|
|
logger.warning("Chunk has empty choices")
|
|
return full_response, full_content, finish_reason
|
|
|
|
# Save basic information of the first response
|
|
if full_response is None:
|
|
full_response = {
|
|
"id": getattr(chunk, 'id', str(uuid.uuid4())),
|
|
"object": "chat.completion.chunk",
|
|
"created": int(datetime.now().timestamp()),
|
|
"model": "models/lpm",
|
|
"system_fingerprint": getattr(chunk, 'system_fingerprint', None),
|
|
"choices": [
|
|
{
|
|
"index": 0,
|
|
"delta": {
|
|
"content": ""
|
|
},
|
|
"finish_reason": None
|
|
}
|
|
]
|
|
}
|
|
|
|
# Collect content and finish reason
|
|
choice = choices[0]
|
|
if hasattr(choice, 'delta'):
|
|
delta = choice.delta
|
|
if hasattr(delta, 'content') and delta.content is not None:
|
|
full_content += delta.content
|
|
# logger.info(f"Added content from OpenAI format, current length: {len(full_content)}")
|
|
if choice.finish_reason:
|
|
finish_reason = choice.finish_reason
|
|
logger.info(f"Got finish_reason: {finish_reason}")
|
|
|
|
return full_response, full_content, finish_reason
|
|
|
|
def collect_stream_response(self, response_iterator: Iterator[Dict[str, Any]]):
|
|
"""
|
|
Collect streaming response into a complete response
|
|
|
|
Args:
|
|
response_iterator: Streaming response iterator
|
|
|
|
Returns:
|
|
Complete response dictionary
|
|
"""
|
|
logger.info("Starting to collect stream response")
|
|
full_response = None
|
|
full_content = ""
|
|
finish_reason = None
|
|
chunk_count = 0
|
|
|
|
try:
|
|
for chunk in response_iterator:
|
|
if chunk is None:
|
|
logger.warning("Received None chunk, skipping")
|
|
continue
|
|
|
|
chunk_count += 1
|
|
# logger.info(f"Processing chunk #{chunk_count}: {chunk}")
|
|
|
|
# Check if it's a custom format response
|
|
is_chat_response = (
|
|
(hasattr(chunk, 'type') and chunk.type == 'chat_response') or
|
|
(isinstance(chunk, dict) and chunk.get("type") == "chat_response")
|
|
)
|
|
|
|
if is_chat_response:
|
|
full_response, full_content, chunk_finish_reason = self._process_chat_response(
|
|
chunk, full_response, full_content
|
|
)
|
|
else:
|
|
full_response, full_content, chunk_finish_reason = self._process_openai_response(
|
|
chunk, full_response, full_content
|
|
)
|
|
|
|
if chunk_finish_reason:
|
|
finish_reason = chunk_finish_reason
|
|
|
|
# logger.info(f"Finished processing all chunks. Total chunks: {chunk_count}")
|
|
# logger.info(f"Final content length: {len(full_content)}")
|
|
# logger.info(f"Final finish_reason: {finish_reason}")
|
|
|
|
if not full_response:
|
|
logger.error("No valid response collected")
|
|
return None
|
|
|
|
if not full_content:
|
|
logger.error("No content collected")
|
|
return None
|
|
|
|
# Update response with complete content
|
|
full_response["choices"][0]["delta"]["content"] = full_content
|
|
if finish_reason:
|
|
full_response["choices"][0]["finish_reason"] = finish_reason
|
|
|
|
# logger.info(f"Final response object: {full_response}")
|
|
# logger.info(f"Final response content: {full_content}")
|
|
return full_response
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error collecting stream response: {str(e)}", exc_info=True)
|
|
return None
|
|
|
|
def chat(
|
|
self,
|
|
request: ChatRequest,
|
|
strategy_chain: Optional[List[Type[SystemPromptStrategy]]] = None,
|
|
stream: bool = True,
|
|
json_response: bool = False,
|
|
client: Optional[Any] = None,
|
|
model_params: Optional[Dict[str, Any]] = None,
|
|
context: Optional[Any] = None,
|
|
) -> Union[Dict[str, Any], Iterator[Dict[str, Any]]]:
|
|
"""
|
|
Main chat method supporting both streaming and non-streaming responses
|
|
|
|
Args:
|
|
request: Chat request containing message and other parameters
|
|
strategy_chain: Optional list of strategy classes to use
|
|
stream: Whether to return a streaming response
|
|
json_response: Whether to request JSON formatted response from LLM
|
|
client: Optional OpenAI client to use. If None, uses local_llm_service.client
|
|
model_params: Optional model specific parameters to override defaults
|
|
context: Optional context to pass to strategies
|
|
|
|
Returns:
|
|
Either an iterator for streaming responses or a single response dictionary
|
|
"""
|
|
logger.info(f"Chat request: {request}")
|
|
# Build messages
|
|
message_builder = MultiTurnMessageBuilder(request, strategy_chain=strategy_chain)
|
|
messages = message_builder.build_messages(context)
|
|
|
|
# Log debug information
|
|
# logger.info("Using strategy chain: %s", [s.__name__ for s in strategy_chain] if strategy_chain else "default")
|
|
logger.info("Final messages for LLM:")
|
|
for msg in messages:
|
|
logger.info(f"Role: {msg['role']}, Content: {msg['content']}")
|
|
|
|
# Use provided client or default local_llm_service.client
|
|
current_client = client if client is not None else local_llm_service.client
|
|
|
|
self.user_llm_config_service = UserLLMConfigService()
|
|
self.user_llm_config = self.user_llm_config_service.get_available_llm()
|
|
|
|
# Prepare API call parameters
|
|
api_params = {
|
|
"messages": messages,
|
|
"temperature": request.temperature,
|
|
"response_format": {"type": "text"},
|
|
"seed": 42, # Optional: Fixed random seed to get consistent responses
|
|
"tools": None, # Optional: If function calling or similar features are needed
|
|
"tool_choice": None, # Optional: If function calling or similar features are needed
|
|
"max_tokens": request.max_tokens,
|
|
"stream": stream,
|
|
"model": request.model or "models/lpm",
|
|
"metadata": request.metadata
|
|
}
|
|
|
|
# Add JSON format requirement (if needed)
|
|
if json_response:
|
|
api_params["response_format"] = {"type": "json_object"}
|
|
|
|
# Update custom model parameters (if provided)
|
|
if model_params:
|
|
api_params.update(model_params)
|
|
|
|
logger.info(f"Current client base URL: {current_client.base_url}")
|
|
# logger.info(f"Using model parameters: {api_params}")
|
|
|
|
# Call LLM API
|
|
try:
|
|
response = current_client.chat.completions.create(**api_params)
|
|
if not stream:
|
|
logger.info(f"Response: {response.json() if hasattr(response, 'json') else response}")
|
|
return response
|
|
|
|
except Exception as e:
|
|
logger.error(f"Chat failed: {str(e)}", exc_info=True)
|
|
raise
|
|
|
|
|
|
# Global chat service instance
|
|
chat_service = ChatService()
|