Construire un agent web intelligent avec FastAPI, LangGraph et MCP

Les assistants basés sur les grands modèles linguistiques (LLM) ne se limitent plus à des échanges conversationnels simples. Un véritable agent web intelligent doit pouvoir comprendre l'intention de l'utilisateur, interagir avec des outils externes et exécuter des tâches complexes en plusieurs étapes.

Architecture du système

L'architecture repose sur trois composants principaux :

  • FastAPI : Interface HTTP pour l'exposition d'API REST
  • LangGraph : Moteur d'orchestration des flux décisionnels de l'agent
  • MCP : Protocole standardisé pour l'intégration d'outils externes

Initialisation de l'application FastAPI

# app/main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
import structlog

logger = structlog.get_logger()

@asynccontextmanager
async def app_lifecycle(app: FastAPI):
    logger.info("Démarrage de l'application")
    yield
    logger.info("Arrêt de l'application")

app = FastAPI(
    title="Agent IA Intelligent",
    lifespan=app_lifecycle
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_methods=["*"],
    allow_headers=["*"]
)

@app.get("/health")
async def health():
    return {"status": "opérationnel"}

@app.get("/")
async def root():
    return {"message": "API Agent IA", "docs": "/docs"}

Gestion des configurations

# app/core/settings.py
from pydantic_settings import BaseSettings

class AppConfig(BaseSettings):
    PROJECT_TITLE: str = "Agent IA API"
    ENV_MODE: str = "development"
    LLM_BACKEND: str = "openai"
    OPENAI_KEY: str = ""
    ANTHROPIC_KEY: str = ""
    DEFAULT_MODEL: str = "gpt-4"
    
    class Config:
        env_file = ".env"

config = AppConfig()

Définition de l'état de l'agent

# app/agent/state.py
from typing import Annotated, TypedDict
from langchain_core.messages import BaseMessage
from langgraph.graph.message import add_messages

class ConversationState(TypedDict):
    history: Annotated[list[BaseMessage], add_messages]

Construction du graphe décisionnel

# app/agent/workflow.py
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from app.agent.state import ConversationState
from app.services.llm import get_model
from app.tools.registry import available_tools

def build_agent_workflow():
    model = get_model()
    bound_model = model.bind_tools(available_tools)
    tool_executor = ToolNode(available_tools)
    
    workflow = StateGraph(ConversationState)
    
    async def reasoning_step(state):
        response = await bound_model.ainvoke(state["history"])
        return {"history": [response]}
    
    def routing_logic(state):
        latest = state["history"][-1]
        if hasattr(latest, "tool_calls") and latest.tool_calls:
            return "execute"
        return END
    
    workflow.add_node("think", reasoning_step)
    workflow.add_node("execute", tool_executor)
    
    workflow.add_edge(START, "think")
    workflow.add_conditional_edges("think", routing_logic, ["execute", END])
    workflow.add_edge("execute", "think")
    
    return workflow.compile()

_agent_instance = None

def get_agent():
    global _agent_instance
    if not _agent_instance:
        _agent_instance = build_agent_workflow()
    return _agent_instance

Intégration d'outils personnnalisés

# app/tools/calculator.py
from langchain_core.tools import tool
from datetime import datetime

@tool
def compute(expression: str) -> str:
    """Évalue une expression mathématique"""
    try:
        result = eval(expression, {"__builtins__": {}})
        return str(result)
    except:
        return "Erreur de calcul"

@tool
def current_datetime() -> str:
    """Retourne la date et heure actuelle"""
    return datetime.now().isoformat()

def list_available_tools():
    return [compute, current_datetime]

Sélection dnyamique du modèle LLM

# app/services/llm.py
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from app.core.settings import config

def get_model():
    if config.LLM_BACKEND == "openai":
        return ChatOpenAI(
            api_key=config.OPENAI_KEY,
            model=config.DEFAULT_MODEL,
            temperature=0.7
        )
    elif config.LLM_BACKEND == "anthropic":
        return ChatAnthropic(
            api_key=config.ANTHROPIC_KEY,
            model=config.DEFAULT_MODEL,
            temperature=0.7
        )
    raise ValueError("Fournisseur LLM non supporté")

Points d'accès API

# app/api/endpoints.py
from fastapi import APIRouter
from pydantic import BaseModel
from app.agent.workflow import get_agent
from langchain_core.messages import HumanMessage

router = APIRouter(prefix="/agent")

class QueryRequest(BaseModel):
    text: str
    session: str = None

class AgentResponse(BaseModel):
    answer: str
    steps: int

@router.post("/process", response_model=AgentResponse)
async def process_query(req: QueryRequest):
    agent = get_agent()
    initial_state = {"history": [HumanMessage(content=req.text)]}
    result = await agent.ainvoke(initial_state)
    
    final_msg = result["history"][-1]
    return AgentResponse(
        answer=final_msg.content,
        steps=len(result["history"])
    )

Exemples d'utilisation

Pour tester le système :

curl -X POST http://localhost:8000/agent/process \
  -H "Content-Type: application/json" \
  -d '{"text": "Quelle est la racine carrée de 144 ?"}'

Perspectives d'amélioration

  • Ajout de persistance des sesisons via PostgreSQL
  • Implémentation de limites de taux d'appel
  • Intégration de traces distribuées avec OpenTelemetry
  • Déploiement de serveurs MCP autonomes pour des fonctionnalités avancées

Étiquettes: FastAPI LangGraph MCP Python ai-agent

Publié le 15 septembre à 21h52