feat: import Excel via chat Léa, suppression nœuds VWB, fix temperature 0.1
- Chat Léa : "importe patients.xlsx" → preview → confirmation → table SQLite Bouton 📎 pour upload fichier, "montre les tables", "info table X" - VWB : suppression nœuds via touche Suppr/Backspace + bouton croix rouge - Fix : toutes les températures VLM à 0.1 (qwen3-vl bloque à 0.0) - Fix : capture VWB avec DISPLAY=:1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -65,8 +65,13 @@ class OllamaClient:
|
||||
max_tokens: int = 500,
|
||||
force_json: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Générer une réponse du VLM
|
||||
|
||||
Générer une réponse du VLM via l'API chat d'Ollama.
|
||||
|
||||
Note: On utilise /api/chat au lieu de /api/generate car qwen3-vl
|
||||
avec /api/generate consomme tous les tokens en thinking interne
|
||||
et retourne une réponse vide. L'API chat gère correctement
|
||||
le mode /no_think et sépare thinking/réponse.
|
||||
|
||||
Args:
|
||||
prompt: Prompt textuel
|
||||
image_path: Chemin vers une image (optionnel)
|
||||
@@ -74,7 +79,8 @@ class OllamaClient:
|
||||
system_prompt: Prompt système (optionnel)
|
||||
temperature: Température de génération
|
||||
max_tokens: Nombre max de tokens
|
||||
|
||||
force_json: Forcer la sortie JSON (non recommandé pour qwen3-vl)
|
||||
|
||||
Returns:
|
||||
Dict avec 'response', 'success', 'error'
|
||||
"""
|
||||
@@ -85,46 +91,52 @@ class OllamaClient:
|
||||
image_data = self._encode_image_from_path(image_path)
|
||||
elif image:
|
||||
image_data = self._encode_image_from_pil(image)
|
||||
|
||||
# Construire la requête avec thinking mode désactivé
|
||||
# Pour Qwen3, utiliser /nothink au début du prompt
|
||||
|
||||
# Construire le prompt avec /no_think pour désactiver le thinking
|
||||
effective_prompt = prompt
|
||||
if "qwen" in self.model.lower():
|
||||
effective_prompt = f"/nothink {prompt}"
|
||||
|
||||
# S'assurer que /no_think est présent (pas de doublon)
|
||||
if "/no_think" not in prompt and "/nothink" not in prompt:
|
||||
effective_prompt = f"/no_think\n{prompt}"
|
||||
|
||||
# Construire le message utilisateur
|
||||
user_message = {"role": "user", "content": effective_prompt}
|
||||
if image_data:
|
||||
user_message["images"] = [image_data]
|
||||
|
||||
# Construire les messages
|
||||
messages = []
|
||||
if system_prompt:
|
||||
messages.append({"role": "system", "content": system_prompt})
|
||||
messages.append(user_message)
|
||||
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"prompt": effective_prompt,
|
||||
"messages": messages,
|
||||
"stream": False,
|
||||
"options": {
|
||||
"temperature": temperature,
|
||||
"num_predict": max_tokens,
|
||||
"num_ctx": 2048, # Contexte réduit pour plus de vitesse
|
||||
"top_k": 1 # Plus rapide pour les tâches de classification
|
||||
"num_ctx": 2048,
|
||||
"top_k": 1
|
||||
}
|
||||
}
|
||||
|
||||
# Forcer la sortie JSON si demandé (réduit drastiquement les erreurs de parsing)
|
||||
if force_json:
|
||||
payload["format"] = "json"
|
||||
|
||||
if system_prompt:
|
||||
payload["system"] = system_prompt
|
||||
|
||||
if image_data:
|
||||
payload["images"] = [image_data]
|
||||
|
||||
# Envoyer la requête
|
||||
# Envoyer la requête via l'API chat
|
||||
response = requests.post(
|
||||
f"{self.endpoint}/api/generate",
|
||||
f"{self.endpoint}/api/chat",
|
||||
json=payload,
|
||||
timeout=self.timeout
|
||||
)
|
||||
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
content = result.get("message", {}).get("content", "")
|
||||
return {
|
||||
"response": result.get("response", ""),
|
||||
"response": content,
|
||||
"success": True,
|
||||
"error": None
|
||||
}
|
||||
@@ -134,7 +146,7 @@ class OllamaClient:
|
||||
"success": False,
|
||||
"error": f"HTTP {response.status_code}: {response.text}"
|
||||
}
|
||||
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"response": "",
|
||||
@@ -197,7 +209,7 @@ Respond with just the type name, nothing else."""
|
||||
if context:
|
||||
prompt += f"\n\nContext: {context}"
|
||||
|
||||
result = self.generate(prompt, image=element_image, temperature=0.0)
|
||||
result = self.generate(prompt, image=element_image, temperature=0.1)
|
||||
|
||||
if result["success"]:
|
||||
element_type = result["response"].strip().lower()
|
||||
@@ -238,7 +250,7 @@ Respond with just the role name, nothing else."""
|
||||
if context:
|
||||
prompt += f"\n\nContext: {context}"
|
||||
|
||||
result = self.generate(prompt, image=element_image, temperature=0.0)
|
||||
result = self.generate(prompt, image=element_image, temperature=0.1)
|
||||
|
||||
if result["success"]:
|
||||
role = result["response"].strip().lower()
|
||||
@@ -266,7 +278,7 @@ Respond with just the role name, nothing else."""
|
||||
"""
|
||||
prompt = "Extract all visible text from this image. Return only the text, nothing else."
|
||||
|
||||
result = self.generate(prompt, image=image, temperature=0.0)
|
||||
result = self.generate(prompt, image=image, temperature=0.1)
|
||||
|
||||
if result["success"]:
|
||||
return {"text": result["response"].strip(), "success": True}
|
||||
@@ -288,30 +300,26 @@ Respond with just the role name, nothing else."""
|
||||
Returns:
|
||||
Dict avec 'type', 'role', 'text', 'confidence', 'success'
|
||||
"""
|
||||
# System prompt direct — pas de thinking, JSON uniquement
|
||||
system_prompt = "You are a JSON-only UI classifier. No thinking. No explanation. Output raw JSON only."
|
||||
|
||||
# User prompt avec exemples explicites pour guider le modèle
|
||||
# Prompt concis sans system prompt — le system prompt avec qwen3-vl
|
||||
# augmente considérablement le nombre de tokens de thinking interne,
|
||||
# causant des réponses vides quand le budget tokens est trop bas.
|
||||
prompt = """/no_think
|
||||
Look at this UI element image and classify it. Reply with ONLY a JSON object, nothing else.
|
||||
|
||||
Classify this UI element. Reply with ONLY a JSON object, nothing else.
|
||||
Types: button, text_input, checkbox, radio, dropdown, tab, link, icon, table_row, menu_item
|
||||
Roles: primary_action, cancel, submit, form_input, search_field, navigation, settings, close, delete, edit, save
|
||||
|
||||
Example 1: {"type": "button", "role": "submit", "text": "OK"}
|
||||
Example 2: {"type": "text_input", "role": "form_input", "text": ""}
|
||||
Example 3: {"type": "icon", "role": "close", "text": "X"}
|
||||
|
||||
Example: {"type": "button", "role": "submit", "text": "OK"}
|
||||
Your answer:"""
|
||||
|
||||
# Note: force_json=False car qwen3-vl ne supporte pas format:json
|
||||
# temperature=0.1 car qwen3-vl bloque à 0.0 avec des images
|
||||
# max_tokens=800 car qwen3-vl consomme 300-700 tokens en thinking
|
||||
# interne même avec /no_think — les images complexes nécessitent
|
||||
# plus de budget pour que la réponse JSON visible soit complète
|
||||
result = self.generate(
|
||||
prompt,
|
||||
image=element_image,
|
||||
system_prompt=system_prompt,
|
||||
temperature=0.1,
|
||||
max_tokens=200,
|
||||
max_tokens=800,
|
||||
force_json=False
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user