4NK_IA_back/services/worker/utils/anythingllm_client.py
Nicolas Cantu 8c089127af docs: Mise à jour complète de la documentation v1.2.0
- Mise à jour du README.md avec les nouvelles fonctionnalités
- Documentation API mise à jour avec les intégrations externes
- Guide d'installation avec bootstrap automatisé
- Architecture mise à jour avec Celery et intégrations
- CHANGELOG détaillé avec toutes les nouvelles fonctionnalités
- Nouvelle documentation des fonctionnalités v1.2.0

Nouvelles sections documentées:
- Pipeline de traitement asynchrone avec Celery
- Intégrations avec APIs externes (Cadastre, Géorisques, BODACC, etc.)
- Clients d'intégration (AnythingLLM, Neo4j, OpenSearch)
- Configuration d'environnement centralisée
- Script bootstrap automatisé
- Monitoring et observabilité
- Exemples d'utilisation et API
2025-09-10 18:45:50 +02:00

412 lines
14 KiB
Python

"""
Client pour l'intégration avec AnythingLLM
"""
import os
import logging
import requests
from typing import Dict, Any, List, Optional
import json
from datetime import datetime
logger = logging.getLogger(__name__)
class AnythingLLMClient:
"""Client pour l'intégration avec AnythingLLM"""
def __init__(self):
self.base_url = os.getenv('ANYLLM_BASE_URL', 'http://anythingllm:3001')
self.api_key = os.getenv('ANYLLM_API_KEY', 'change_me')
# Configuration des workspaces
self.workspaces = {
'normes': os.getenv('ANYLLM_WORKSPACE_NORMES', 'workspace_normes'),
'trames': os.getenv('ANYLLM_WORKSPACE_TRAMES', 'workspace_trames'),
'actes': os.getenv('ANYLLM_WORKSPACE_ACTES', 'workspace_actes')
}
self.session = requests.Session()
self.session.headers.update({
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json'
})
async def create_workspace(self, name: str, description: str = None) -> Dict[str, Any]:
"""
Création d'un workspace AnythingLLM
Args:
name: Nom du workspace
description: Description du workspace
Returns:
Résultat de la création
"""
logger.info(f"🏢 Création du workspace AnythingLLM: {name}")
try:
payload = {
'name': name,
'description': description or f"Workspace {name} pour le pipeline notarial",
'openAiTemp': 0.7,
'openAiHistory': 20,
'openAiMaxTokens': 4000,
'openAiModel': 'gpt-3.5-turbo',
'embeddingsEngine': 'openai',
'embeddingsModel': 'text-embedding-ada-002',
'vectorTag': name.lower().replace(' ', '_')
}
response = self.session.post(
f"{self.base_url}/api/workspace/new",
json=payload,
timeout=30
)
if response.status_code == 200:
data = response.json()
logger.info(f"✅ Workspace {name} créé avec succès")
return {
'status': 'created',
'workspace_id': data.get('id'),
'workspace_name': name,
'created_at': datetime.now().isoformat()
}
else:
logger.error(f"Erreur lors de la création du workspace: {response.status_code}")
return {
'status': 'error',
'error': f"Erreur API: {response.status_code}",
'response': response.text
}
except Exception as e:
logger.error(f"Erreur lors de la création du workspace {name}: {e}")
return {
'status': 'error',
'error': str(e)
}
async def upload_document(self, workspace_id: str, document_data: bytes, filename: str,
metadata: Dict[str, Any] = None) -> Dict[str, Any]:
"""
Upload d'un document dans un workspace
Args:
workspace_id: ID du workspace
document_data: Données du document
filename: Nom du fichier
metadata: Métadonnées du document
Returns:
Résultat de l'upload
"""
logger.info(f"📄 Upload du document {filename} dans le workspace {workspace_id}")
try:
# Préparation des fichiers
files = {
'file': (filename, document_data, 'application/octet-stream')
}
# Préparation des données
data = {
'workspaceId': workspace_id,
'chunkSize': 1000,
'chunkOverlap': 200
}
if metadata:
data['metadata'] = json.dumps(metadata)
# Suppression de l'header Content-Type pour les multipart
headers = {'Authorization': f'Bearer {self.api_key}'}
response = requests.post(
f"{self.base_url}/api/workspace/{workspace_id}/upload",
files=files,
data=data,
headers=headers,
timeout=60
)
if response.status_code == 200:
data = response.json()
logger.info(f"✅ Document {filename} uploadé avec succès")
return {
'status': 'uploaded',
'document_id': data.get('id'),
'filename': filename,
'workspace_id': workspace_id,
'chunks_created': data.get('chunks', 0),
'uploaded_at': datetime.now().isoformat()
}
else:
logger.error(f"Erreur lors de l'upload: {response.status_code}")
return {
'status': 'error',
'error': f"Erreur API: {response.status_code}",
'response': response.text
}
except Exception as e:
logger.error(f"Erreur lors de l'upload du document {filename}: {e}")
return {
'status': 'error',
'error': str(e)
}
async def search_documents(self, workspace_id: str, query: str,
limit: int = 10) -> Dict[str, Any]:
"""
Recherche dans les documents d'un workspace
Args:
workspace_id: ID du workspace
query: Requête de recherche
limit: Nombre maximum de résultats
Returns:
Résultats de la recherche
"""
logger.info(f"🔍 Recherche dans le workspace {workspace_id}: {query}")
try:
payload = {
'workspaceId': workspace_id,
'query': query,
'mode': 'chat',
'maxTokens': 4000,
'temperature': 0.7,
'topK': limit
}
response = self.session.post(
f"{self.base_url}/api/workspace/{workspace_id}/chat",
json=payload,
timeout=30
)
if response.status_code == 200:
data = response.json()
logger.info(f"✅ Recherche terminée, {len(data.get('sources', []))} résultats")
return {
'status': 'completed',
'query': query,
'workspace_id': workspace_id,
'results': data.get('sources', []),
'response': data.get('text', ''),
'searched_at': datetime.now().isoformat()
}
else:
logger.error(f"Erreur lors de la recherche: {response.status_code}")
return {
'status': 'error',
'error': f"Erreur API: {response.status_code}",
'response': response.text
}
except Exception as e:
logger.error(f"Erreur lors de la recherche: {e}")
return {
'status': 'error',
'error': str(e)
}
async def get_workspace_info(self, workspace_id: str) -> Dict[str, Any]:
"""
Récupération des informations d'un workspace
Args:
workspace_id: ID du workspace
Returns:
Informations du workspace
"""
try:
response = self.session.get(
f"{self.base_url}/api/workspace/{workspace_id}",
timeout=10
)
if response.status_code == 200:
data = response.json()
return {
'status': 'found',
'workspace': data,
'retrieved_at': datetime.now().isoformat()
}
else:
return {
'status': 'error',
'error': f"Erreur API: {response.status_code}"
}
except Exception as e:
logger.error(f"Erreur lors de la récupération du workspace: {e}")
return {
'status': 'error',
'error': str(e)
}
async def list_workspaces(self) -> Dict[str, Any]:
"""
Liste tous les workspaces disponibles
Returns:
Liste des workspaces
"""
try:
response = self.session.get(
f"{self.base_url}/api/workspaces",
timeout=10
)
if response.status_code == 200:
data = response.json()
return {
'status': 'success',
'workspaces': data.get('workspaces', []),
'count': len(data.get('workspaces', [])),
'retrieved_at': datetime.now().isoformat()
}
else:
return {
'status': 'error',
'error': f"Erreur API: {response.status_code}"
}
except Exception as e:
logger.error(f"Erreur lors de la liste des workspaces: {e}")
return {
'status': 'error',
'error': str(e)
}
async def index_document_for_actes(self, doc_id: str, text: str,
entities: Dict[str, Any],
doc_type: str) -> Dict[str, Any]:
"""
Indexation d'un document dans le workspace des actes
Args:
doc_id: ID du document
text: Texte du document
entities: Entités extraites
doc_type: Type de document
Returns:
Résultat de l'indexation
"""
logger.info(f"📚 Indexation du document {doc_id} dans le workspace actes")
try:
# Préparation du contenu structuré
structured_content = self._prepare_structured_content(doc_id, text, entities, doc_type)
# Upload du contenu structuré
workspace_id = await self._get_workspace_id('actes')
if not workspace_id:
return {
'status': 'error',
'error': 'Workspace actes non trouvé'
}
filename = f"{doc_id}_structured.txt"
document_data = structured_content.encode('utf-8')
result = await self.upload_document(workspace_id, document_data, filename, {
'doc_id': doc_id,
'doc_type': doc_type,
'entities': entities,
'indexed_at': datetime.now().isoformat()
})
return result
except Exception as e:
logger.error(f"Erreur lors de l'indexation du document {doc_id}: {e}")
return {
'status': 'error',
'error': str(e)
}
async def search_similar_actes(self, doc_type: str, entities: Dict[str, Any]) -> Dict[str, Any]:
"""
Recherche d'actes similaires
Args:
doc_type: Type de document
entities: Entités extraites
Returns:
Actes similaires trouvés
"""
logger.info(f"🔍 Recherche d'actes similaires pour le type: {doc_type}")
try:
workspace_id = await self._get_workspace_id('actes')
if not workspace_id:
return {
'status': 'error',
'error': 'Workspace actes non trouvé'
}
# Construction de la requête de recherche
query = self._build_similarity_query(doc_type, entities)
result = await self.search_documents(workspace_id, query, limit=5)
return result
except Exception as e:
logger.error(f"Erreur lors de la recherche d'actes similaires: {e}")
return {
'status': 'error',
'error': str(e)
}
def _prepare_structured_content(self, doc_id: str, text: str,
entities: Dict[str, Any], doc_type: str) -> str:
"""Prépare le contenu structuré pour l'indexation"""
content = f"""DOCUMENT ID: {doc_id}
TYPE: {doc_type}
DATE D'INDEXATION: {datetime.now().isoformat()}
ENTITÉS EXTRAITES:
{json.dumps(entities, indent=2, ensure_ascii=False)}
TEXTE DU DOCUMENT:
{text}
---
Ce document a été traité par le pipeline notarial v1.2.0
"""
return content
def _build_similarity_query(self, doc_type: str, entities: Dict[str, Any]) -> str:
"""Construit une requête de recherche pour trouver des actes similaires"""
query_parts = [f"type:{doc_type}"]
# Ajout des entités importantes
if 'vendeur' in entities:
query_parts.append(f"vendeur:{entities['vendeur'].get('nom', '')}")
if 'acheteur' in entities:
query_parts.append(f"acheteur:{entities['acheteur'].get('nom', '')}")
if 'bien' in entities:
query_parts.append(f"adresse:{entities['bien'].get('adresse', '')}")
return " ".join(query_parts)
async def _get_workspace_id(self, workspace_name: str) -> Optional[str]:
"""Récupère l'ID d'un workspace par son nom"""
try:
workspaces_result = await self.list_workspaces()
if workspaces_result['status'] == 'success':
for workspace in workspaces_result['workspaces']:
if workspace.get('name') == workspace_name:
return workspace.get('id')
return None
except Exception as e:
logger.error(f"Erreur lors de la récupération de l'ID du workspace {workspace_name}: {e}")
return None