feat(agent_chat): Ajouter mode Agent Libre avec planification LLM
- Nouveau module autonomous_planner.py pour planification intelligente - Utilise Qwen via Ollama pour décomposer les tâches en actions - Actions supportées: open_url, click, type_text, hotkey, scroll, wait - Intégration OWL-v2 et VLM pour détection visuelle intelligente - Nouvelle interface chat conversationnelle (chat.html) - Prompt LLM générique adaptable à toute demande - Endpoints API: /api/agent/plan, /api/agent/execute, /api/agent/status Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,15 @@ from .conversation_manager import (
|
|||||||
get_conversation_manager
|
get_conversation_manager
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .autonomous_planner import (
|
||||||
|
AutonomousPlanner,
|
||||||
|
ExecutionPlan,
|
||||||
|
PlannedAction,
|
||||||
|
ActionResult,
|
||||||
|
ActionType,
|
||||||
|
get_autonomous_planner
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Intent Parser
|
# Intent Parser
|
||||||
"IntentParser",
|
"IntentParser",
|
||||||
@@ -66,4 +75,11 @@ __all__ = [
|
|||||||
"ConversationTurn",
|
"ConversationTurn",
|
||||||
"ConversationContext",
|
"ConversationContext",
|
||||||
"get_conversation_manager",
|
"get_conversation_manager",
|
||||||
|
# Autonomous Planner (Agent Libre)
|
||||||
|
"AutonomousPlanner",
|
||||||
|
"ExecutionPlan",
|
||||||
|
"PlannedAction",
|
||||||
|
"ActionResult",
|
||||||
|
"ActionType",
|
||||||
|
"get_autonomous_planner",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from .intent_parser import IntentParser, IntentType, get_intent_parser
|
|||||||
from .confirmation import ConfirmationLoop, ConfirmationStatus, RiskLevel, get_confirmation_loop
|
from .confirmation import ConfirmationLoop, ConfirmationStatus, RiskLevel, get_confirmation_loop
|
||||||
from .response_generator import ResponseGenerator, get_response_generator
|
from .response_generator import ResponseGenerator, get_response_generator
|
||||||
from .conversation_manager import ConversationManager, get_conversation_manager
|
from .conversation_manager import ConversationManager, get_conversation_manager
|
||||||
|
from .autonomous_planner import AutonomousPlanner, get_autonomous_planner, ExecutionPlan
|
||||||
|
|
||||||
# GPU Resource Manager (optional)
|
# GPU Resource Manager (optional)
|
||||||
try:
|
try:
|
||||||
@@ -74,6 +75,7 @@ intent_parser: Optional[IntentParser] = None
|
|||||||
confirmation_loop: Optional[ConfirmationLoop] = None
|
confirmation_loop: Optional[ConfirmationLoop] = None
|
||||||
response_generator: Optional[ResponseGenerator] = None
|
response_generator: Optional[ResponseGenerator] = None
|
||||||
conversation_manager: Optional[ConversationManager] = None
|
conversation_manager: Optional[ConversationManager] = None
|
||||||
|
autonomous_planner: Optional[AutonomousPlanner] = None
|
||||||
|
|
||||||
# Execution components
|
# Execution components
|
||||||
workflow_pipeline = None
|
workflow_pipeline = None
|
||||||
@@ -95,6 +97,7 @@ def init_system():
|
|||||||
"""Initialiser tous les composants du système."""
|
"""Initialiser tous les composants du système."""
|
||||||
global matcher, gpu_manager
|
global matcher, gpu_manager
|
||||||
global intent_parser, confirmation_loop, response_generator, conversation_manager
|
global intent_parser, confirmation_loop, response_generator, conversation_manager
|
||||||
|
global autonomous_planner
|
||||||
|
|
||||||
# 1. SemanticMatcher
|
# 1. SemanticMatcher
|
||||||
try:
|
try:
|
||||||
@@ -178,6 +181,24 @@ def init_system():
|
|||||||
else:
|
else:
|
||||||
logger.info("ℹ Mode simulation (composants d'exécution non disponibles)")
|
logger.info("ℹ Mode simulation (composants d'exécution non disponibles)")
|
||||||
|
|
||||||
|
# 5. Autonomous Planner (Agent Libre)
|
||||||
|
try:
|
||||||
|
autonomous_planner = get_autonomous_planner(llm_model="qwen2.5:7b")
|
||||||
|
|
||||||
|
# Configurer les callbacks pour l'exécution
|
||||||
|
if screen_capturer:
|
||||||
|
autonomous_planner.set_screen_capturer(screen_capturer.capture)
|
||||||
|
|
||||||
|
def progress_callback(data):
|
||||||
|
socketio.emit('agent_progress', data)
|
||||||
|
|
||||||
|
autonomous_planner.set_progress_callback(progress_callback)
|
||||||
|
|
||||||
|
logger.info(f"✓ AutonomousPlanner initialisé (LLM: {autonomous_planner.llm_available})")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"⚠ AutonomousPlanner: {e}")
|
||||||
|
autonomous_planner = None
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Routes Web
|
# Routes Web
|
||||||
@@ -185,7 +206,13 @@ def init_system():
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
"""Page principale."""
|
"""Page principale - nouvelle interface chat."""
|
||||||
|
return render_template('chat.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/classic')
|
||||||
|
def classic():
|
||||||
|
"""Ancienne interface (fallback)."""
|
||||||
return render_template('command.html')
|
return render_template('command.html')
|
||||||
|
|
||||||
|
|
||||||
@@ -614,6 +641,174 @@ def api_llm_set_model():
|
|||||||
return jsonify({"success": False, "error": "IntentParser non initialisé"})
|
return jsonify({"success": False, "error": "IntentParser non initialisé"})
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# API Agent Libre (Autonomous Mode)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@app.route('/api/agent/plan', methods=['POST'])
|
||||||
|
def api_agent_plan():
|
||||||
|
"""
|
||||||
|
Génère un plan d'exécution pour une tâche en langage naturel.
|
||||||
|
|
||||||
|
Le mode "Agent Libre" permet d'exécuter des tâches sans workflow pré-enregistré.
|
||||||
|
Le LLM (Qwen) décompose la demande en étapes d'actions.
|
||||||
|
"""
|
||||||
|
if not autonomous_planner:
|
||||||
|
return jsonify({"error": "Agent autonome non disponible"}), 503
|
||||||
|
|
||||||
|
data = request.json
|
||||||
|
user_request = data.get('request', '').strip()
|
||||||
|
|
||||||
|
if not user_request:
|
||||||
|
return jsonify({"error": "Requête vide"}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Contexte optionnel (écran actuel, etc.)
|
||||||
|
context = data.get('context', {})
|
||||||
|
|
||||||
|
# Générer le plan
|
||||||
|
plan = autonomous_planner.plan(user_request, context)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"success": True,
|
||||||
|
"plan": {
|
||||||
|
"task": plan.task_description,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step": s.step_number,
|
||||||
|
"action": s.action_type.value,
|
||||||
|
"description": s.description,
|
||||||
|
"target": s.target,
|
||||||
|
"params": s.parameters,
|
||||||
|
"expected_result": s.expected_result
|
||||||
|
}
|
||||||
|
for s in plan.steps
|
||||||
|
],
|
||||||
|
"estimated_seconds": plan.estimated_duration_seconds,
|
||||||
|
"risk_level": plan.risk_level,
|
||||||
|
"requires_confirmation": plan.requires_confirmation
|
||||||
|
},
|
||||||
|
"llm_available": autonomous_planner.llm_available
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Agent plan error: {e}")
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/agent/execute', methods=['POST'])
|
||||||
|
def api_agent_execute():
|
||||||
|
"""
|
||||||
|
Exécute un plan d'agent autonome.
|
||||||
|
|
||||||
|
Attend un objet plan (généré par /api/agent/plan) et l'exécute étape par étape.
|
||||||
|
"""
|
||||||
|
if not autonomous_planner:
|
||||||
|
return jsonify({"error": "Agent autonome non disponible"}), 503
|
||||||
|
|
||||||
|
data = request.json
|
||||||
|
plan_data = data.get('plan')
|
||||||
|
|
||||||
|
if not plan_data:
|
||||||
|
return jsonify({"error": "Plan manquant"}), 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Reconstruire le plan depuis les données
|
||||||
|
from .autonomous_planner import PlannedAction, ActionType
|
||||||
|
|
||||||
|
steps = []
|
||||||
|
for step_data in plan_data.get('steps', []):
|
||||||
|
action_type_str = step_data.get('action', 'click')
|
||||||
|
action_type_map = {
|
||||||
|
'open_app': ActionType.OPEN_APP,
|
||||||
|
'open_url': ActionType.OPEN_URL,
|
||||||
|
'click': ActionType.CLICK,
|
||||||
|
'type_text': ActionType.TYPE_TEXT,
|
||||||
|
'hotkey': ActionType.HOTKEY,
|
||||||
|
'scroll': ActionType.SCROLL,
|
||||||
|
'wait': ActionType.WAIT,
|
||||||
|
'screenshot': ActionType.SCREENSHOT
|
||||||
|
}
|
||||||
|
|
||||||
|
steps.append(PlannedAction(
|
||||||
|
step_number=step_data.get('step', len(steps) + 1),
|
||||||
|
action_type=action_type_map.get(action_type_str, ActionType.CLICK),
|
||||||
|
description=step_data.get('description', ''),
|
||||||
|
target=step_data.get('target'),
|
||||||
|
parameters=step_data.get('params', {}),
|
||||||
|
expected_result=step_data.get('expected_result')
|
||||||
|
))
|
||||||
|
|
||||||
|
plan = ExecutionPlan(
|
||||||
|
task_description=plan_data.get('task', ''),
|
||||||
|
steps=steps,
|
||||||
|
estimated_duration_seconds=plan_data.get('estimated_seconds', 30),
|
||||||
|
risk_level=plan_data.get('risk_level', 'low')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exécuter en arrière-plan
|
||||||
|
socketio.start_background_task(execute_agent_plan, plan)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"success": True,
|
||||||
|
"message": "Exécution démarrée",
|
||||||
|
"steps_count": len(steps)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Agent execute error: {e}")
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/agent/status')
|
||||||
|
def api_agent_status():
|
||||||
|
"""Statut de l'agent autonome."""
|
||||||
|
return jsonify({
|
||||||
|
"available": autonomous_planner is not None,
|
||||||
|
"llm_available": autonomous_planner.llm_available if autonomous_planner else False,
|
||||||
|
"llm_model": autonomous_planner.llm_model if autonomous_planner else None
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def execute_agent_plan(plan: ExecutionPlan):
|
||||||
|
"""Exécute un plan d'agent en arrière-plan."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
try:
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
results = loop.run_until_complete(autonomous_planner.execute_plan(plan))
|
||||||
|
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
# Envoyer le résultat final
|
||||||
|
success_count = sum(1 for r in results if r.success)
|
||||||
|
total = len(results)
|
||||||
|
|
||||||
|
socketio.emit('execution_completed', {
|
||||||
|
"success": success_count == total,
|
||||||
|
"workflow": plan.task_description,
|
||||||
|
"message": f"{success_count}/{total} étapes réussies",
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"step": r.action.step_number,
|
||||||
|
"success": r.success,
|
||||||
|
"message": r.message
|
||||||
|
}
|
||||||
|
for r in results
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Agent execution error: {e}")
|
||||||
|
socketio.emit('execution_completed', {
|
||||||
|
"success": False,
|
||||||
|
"workflow": plan.task_description,
|
||||||
|
"message": f"Erreur: {str(e)}"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/help')
|
@app.route('/api/help')
|
||||||
def api_help():
|
def api_help():
|
||||||
"""Aide et mode d'emploi."""
|
"""Aide et mode d'emploi."""
|
||||||
|
|||||||
1013
agent_chat/autonomous_planner.py
Normal file
1013
agent_chat/autonomous_planner.py
Normal file
File diff suppressed because it is too large
Load Diff
1187
agent_chat/templates/chat.html
Normal file
1187
agent_chat/templates/chat.html
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user