feat:chatbot weiterimplementiert

This commit is contained in:
Ida Dittrich 2026-01-05 15:12:13 +01:00
parent 2672266eca
commit 3a5d27f385
4 changed files with 667 additions and 888 deletions

View file

@ -39,15 +39,17 @@ class PreprocessorConnector:
logger.info("PreprocessorConnector initialized")
async def executeQuery(self, sql_query: str) -> str:
async def executeQuery(self, sql_query: str, return_json: bool = False):
"""
Execute a SQL query via the preprocessing API.
Args:
sql_query: SQL SELECT query to execute
return_json: If True, returns dict with 'text' and 'data' keys. If False, returns formatted string.
Returns:
Formatted result string with query results
If return_json=False: Formatted result string with query results
If return_json=True: Dict with 'text' (formatted string) and 'data' (raw JSON data list)
Raises:
ValueError: If query is invalid or contains forbidden keywords
@ -57,16 +59,22 @@ class PreprocessorConnector:
# Validate query
validation_error = self._validateQuery(sql_query)
if validation_error:
if return_json:
return {"text": validation_error, "data": []}
return validation_error
# Check configuration
if not self.api_key:
error_msg = "Error: PP_QUERY_API_KEY not configured"
logger.error(error_msg)
if return_json:
return {"text": error_msg, "data": []}
return error_msg
if not self.base_url:
error_msg = "Error: PP_QUERY_BASE_URL not configured"
logger.error(error_msg)
if return_json:
return {"text": error_msg, "data": []}
return error_msg
# Make HTTP POST request to preprocessing API
@ -86,7 +94,10 @@ class PreprocessorConnector:
# Parse response
if not result.get("success"):
error_message = result.get("message", "Unknown error")
return f"Query failed: {error_message}"
error_text = f"Query failed: {error_message}"
if return_json:
return {"text": error_text, "data": []}
return error_text
# Format results
data = result.get("data", [])
@ -97,7 +108,10 @@ class PreprocessorConnector:
# Format results as string
if not display_data:
return f"Query executed successfully. Returned {row_count} rows (no data)."
result_text = f"Query executed successfully. Returned {row_count} rows (no data)."
if return_json:
return {"text": result_text, "data": data}
return result_text
# Format each row
results = []
@ -110,6 +124,8 @@ class PreprocessorConnector:
+ "\n".join(results)
)
if return_json:
return {"text": result_text, "data": data}
return result_text
except httpx.HTTPStatusError as e:

File diff suppressed because it is too large Load diff

View file

@ -1004,24 +1004,36 @@ class ChatObjects:
# Create documents in normalized documents table
created_documents = []
for doc_data in documents_to_create:
# Normalize to plain dict before assignment
if isinstance(doc_data, ChatDocument):
doc_dict = doc_data.model_dump()
elif isinstance(doc_data, dict):
doc_dict = dict(doc_data)
else:
# Attempt to coerce to ChatDocument then dump
try:
doc_dict = ChatDocument(**doc_data).model_dump()
except Exception:
logger.error("Invalid document data type for message creation")
continue
doc_dict["messageId"] = createdMessage["id"]
created_doc = self.createDocument(doc_dict)
if created_doc:
created_documents.append(created_doc)
logger.debug(f"Creating {len(documents_to_create)} document(s) for message {createdMessage['id']}")
for idx, doc_data in enumerate(documents_to_create):
try:
# Normalize to plain dict before assignment
if isinstance(doc_data, ChatDocument):
doc_dict = doc_data.model_dump()
elif isinstance(doc_data, dict):
doc_dict = dict(doc_data)
else:
# Attempt to coerce to ChatDocument then dump
try:
doc_dict = ChatDocument(**doc_data).model_dump()
except Exception as e:
logger.error(f"Invalid document data type for message creation (document {idx + 1}/{len(documents_to_create)}): {e}")
continue
# Ensure messageId is set
doc_dict["messageId"] = createdMessage["id"]
logger.debug(f"Creating document {idx + 1}/{len(documents_to_create)}: fileName={doc_dict.get('fileName', 'unknown')}, fileId={doc_dict.get('fileId', 'unknown')}, messageId={doc_dict.get('messageId', 'unknown')}")
created_doc = self.createDocument(doc_dict)
if created_doc:
created_documents.append(created_doc)
logger.debug(f"Successfully created document {idx + 1}/{len(documents_to_create)}: {created_doc.fileName} (id: {created_doc.id})")
else:
logger.error(f"Failed to create document {idx + 1}/{len(documents_to_create)}: createDocument returned None for fileName={doc_dict.get('fileName', 'unknown')}")
except Exception as e:
logger.error(f"Error processing document {idx + 1}/{len(documents_to_create)}: {e}", exc_info=True)
logger.info(f"Created {len(created_documents)}/{len(documents_to_create)} document(s) for message {createdMessage['id']}")
# Convert to ChatMessage model
chat_message = ChatMessage(
@ -1256,12 +1268,18 @@ class ChatObjects:
try:
# Validate and normalize document data to dict
document = ChatDocument(**documentData)
logger.debug(f"Creating document in database: fileName={document.fileName}, fileId={document.fileId}, messageId={document.messageId}")
created = self.db.recordCreate(ChatDocument, document.model_dump())
return ChatDocument(**created)
if created:
created_doc = ChatDocument(**created)
logger.debug(f"Successfully created document in database: {created_doc.fileName} (id: {created_doc.id})")
return created_doc
else:
logger.error(f"Failed to create document in database: recordCreate returned None for fileName={document.fileName}")
return None
except Exception as e:
logger.error(f"Error creating message document: {str(e)}")
logger.error(f"Error creating message document: {str(e)}", exc_info=True)
return None

View file

@ -44,12 +44,18 @@ async def process(self, parameters: Dict[str, Any]) -> ActionResult:
# Convert to DocumentReferenceList if needed
if documentListParam is None:
documentList = DocumentReferenceList(references=[])
logger.debug(f"ai.process: documentList is None, using empty DocumentReferenceList")
elif isinstance(documentListParam, DocumentReferenceList):
documentList = documentListParam
logger.info(f"ai.process: Received DocumentReferenceList with {len(documentList.references)} references")
for idx, ref in enumerate(documentList.references):
logger.info(f" Reference {idx + 1}: documentId={ref.documentId}, type={type(ref).__name__}")
elif isinstance(documentListParam, str):
documentList = DocumentReferenceList.from_string_list([documentListParam])
logger.info(f"ai.process: Converted string to DocumentReferenceList with {len(documentList.references)} references")
elif isinstance(documentListParam, list):
documentList = DocumentReferenceList.from_string_list(documentListParam)
logger.info(f"ai.process: Converted list to DocumentReferenceList with {len(documentList.references)} references")
else:
logger.error(f"Invalid documentList type: {type(documentListParam)}")
documentList = DocumentReferenceList(references=[])
@ -152,9 +158,16 @@ async def process(self, parameters: Dict[str, Any]) -> ActionResult:
)
else:
# Full mode: use unified callAiContent method
# For document generation (xlsx, docx, pdf, etc.), use DATA_GENERATE with document intent
from modules.datamodels.datamodelAi import OperationTypeEnum
# Always use DATA_GENERATE with document intent for ai.process
# This ensures proper document generation pipeline is used
options = AiCallOptions(
resultFormat=output_format
resultFormat=output_format,
operationType=OperationTypeEnum.DATA_GENERATE
)
generation_intent = "document"
# Update progress - calling AI
self.services.chat.progressLogUpdate(operationId, 0.6, "Calling AI")
@ -171,17 +184,23 @@ async def process(self, parameters: Dict[str, Any]) -> ActionResult:
options=options,
contentParts=contentParts, # Pre-extracted ContentParts
outputFormat=output_format,
parentOperationId=operationId
parentOperationId=operationId,
generationIntent=generation_intent
)
else:
# Pass documentList - callAiContent handles Phases 5A-5E internally
# This includes automatic detection of ContentExtracted documents
logger.info(f"ai.process: Calling callAiContent with {len(documentList.references)} document references")
if documentList.references:
for idx, ref in enumerate(documentList.references):
logger.info(f" Passing reference {idx + 1}: documentId={ref.documentId}")
aiResponse = await self.services.ai.callAiContent(
prompt=aiPrompt,
options=options,
documentList=documentList, # callAiContent macht Phasen 5A-5E
outputFormat=output_format,
parentOperationId=operationId
parentOperationId=operationId,
generationIntent=generation_intent
)
# Update progress - processing result