Compare commits

..

3 Commits

2 changed files with 32 additions and 8 deletions

View File

@@ -12,6 +12,7 @@ import hashlib
import json import json
from typing import Dict, Any, Optional, List, Tuple from typing import Dict, Any, Optional, List, Tuple
from datetime import datetime from datetime import datetime
from urllib.parse import unquote
from services.sync_utils_base import BaseSyncUtils from services.sync_utils_base import BaseSyncUtils
from services.models import ( from services.models import (

View File

@@ -63,14 +63,31 @@ class XAIService:
Raises: Raises:
RuntimeError: bei HTTP-Fehler oder fehlendem file_id in der Antwort RuntimeError: bei HTTP-Fehler oder fehlendem file_id in der Antwort
""" """
self._log(f"📤 Uploading {len(file_content)} bytes to xAI: {filename}") # Normalize MIME type: xAI needs correct Content-Type for proper processing
# If generic octet-stream but file is clearly a PDF, fix it
if mime_type == 'application/octet-stream' and filename.lower().endswith('.pdf'):
mime_type = 'application/pdf'
self._log(f"⚠️ Corrected MIME type to application/pdf for {filename}")
self._log(f"📤 Uploading {len(file_content)} bytes to xAI: {filename} ({mime_type})")
session = await self._get_session() session = await self._get_session()
url = f"{XAI_FILES_URL}/v1/files" url = f"{XAI_FILES_URL}/v1/files"
headers = {"Authorization": f"Bearer {self.api_key}"} headers = {"Authorization": f"Bearer {self.api_key}"}
form = aiohttp.FormData() # Create multipart form with explicit UTF-8 filename encoding
form.add_field('file', file_content, filename=filename, content_type=mime_type) # aiohttp automatically URL-encodes filenames with special chars,
# but xAI expects raw UTF-8 in the filename parameter
form = aiohttp.FormData(quote_fields=False)
form.add_field(
'file',
file_content,
filename=filename,
content_type=mime_type
)
# CRITICAL: purpose="file_search" enables proper PDF processing
# Without this, xAI throws "internal error" on complex PDFs
form.add_field('purpose', 'file_search')
async with session.post(url, data=form, headers=headers) as response: async with session.post(url, data=form, headers=headers) as response:
try: try:
@@ -95,9 +112,12 @@ class XAIService:
async def add_to_collection(self, collection_id: str, file_id: str) -> None: async def add_to_collection(self, collection_id: str, file_id: str) -> None:
""" """
Fügt eine Datei einer xAI-Collection hinzu. Fügt eine Datei einer xAI-Collection (Vector Store) hinzu.
POST https://management-api.x.ai/v1/collections/{collection_id}/documents/{file_id} POST https://api.x.ai/v1/vector_stores/{vector_store_id}/files
Uses the OpenAI-compatible API pattern for adding files to vector stores.
This triggers proper indexing and processing.
Raises: Raises:
RuntimeError: bei HTTP-Fehler RuntimeError: bei HTTP-Fehler
@@ -105,13 +125,16 @@ class XAIService:
self._log(f"📚 Adding file {file_id} to collection {collection_id}") self._log(f"📚 Adding file {file_id} to collection {collection_id}")
session = await self._get_session() session = await self._get_session()
url = f"{XAI_MANAGEMENT_URL}/v1/collections/{collection_id}/documents/{file_id}" # Use the OpenAI-compatible endpoint (not management API)
url = f"{XAI_FILES_URL}/v1/vector_stores/{collection_id}/files"
headers = { headers = {
"Authorization": f"Bearer {self.management_key}", "Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json", "Content-Type": "application/json",
} }
async with session.post(url, headers=headers) as response: payload = {"file_id": file_id}
async with session.post(url, json=payload, headers=headers) as response:
if response.status not in (200, 201): if response.status not in (200, 201):
raw = await response.text() raw = await response.text()
raise RuntimeError( raise RuntimeError(