test tse

import os
import json
import logging
import gradio as gr
from pydantic import BaseModel, Field
from openai import OpenAI
from langchain.vectorstores import FAISS
from langchain.embeddings import HuggingFaceEmbeddings
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain_core.tools import Tool
from langgraph.graph import MessagesState, StateGraph, START

# ============================================================
# 1️⃣ LOGGING
# ============================================================
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] %(message)s')
log = logging.getLogger(__name__)

# ============================================================
# 2️⃣ CLIENT GPT-OSS
# ============================================================
client = OpenAI(
   base_url="https://router.huggingface.co/v1",
   api_key=os.getenv("HF_TOKEN")
)

# ============================================================
# 3️⃣ PROMPT SYSTÈME
# ============================================================
PROMPT_SYSTEM = """
Tu es un assistant expert en recertification d’applications.

Tu disposes des outils suivants :
- smart_get_info : Donne l'équipe responsable et la classification d'une application.
- smart_audit_access : Liste les derniers accès à une application.
- smart_recertification_status : Informe si une application doit être recertifiée.
- smart_rag_recertification_context : Recherche dans la base RAG les définitions, étapes et acteurs pour lancer la recertification d'une application.

Règles :
- Si la question est générale (ex. « c’est quoi une recertification ? »), réponds directement sans appeler d’outil.
- Si une information précise est demandée sur une application, appelle un outil si besoin.
- Ne répète jamais un outil si tu as déjà reçu l’Observation.
- Réponds toujours de façon claire, concise et utile.
"""

# ============================================================
# 4️⃣ TOOLS avec args_schema (BaseModel)
# ============================================================

# ---- 1. Tool  smart_get_info  ----
class SmartGetInfoArgs(BaseModel):
   name: str = Field(..., description="Nom exact de l'application.")
   description: str = Field("", description="Courte description de l'application.")
   keywords: str = Field("", description="Mots-clés associés à l'application.")

def smart_get_info(name: str, description: str, keywords: str) -> str:
   log.info(f"[TOOL] smart_get_info(name={name}, description={description}, keywords={keywords})")
   return (
       f"L'application '{name}' ({keywords}) est décrite comme '{description}'. "
       "Elle est gérée par l'équipe Développement et classée 'Confidentielle'."
   )

# ---- 2. Tool  smart_audit_access  ----
class SmartAuditAccessArgs(BaseModel):
   app: str = Field(..., description="Nom de l'application pour laquelle afficher les derniers accès.")

def smart_audit_access(app: str) -> str:
   log.info(f"[TOOL] smart_audit_access: {app}")
   return f"Derniers accès à {app} : UserX, UserY."

# ---- 3. Tool  smart_recertification_status  ----
class SmartRecertificationStatusArgs(BaseModel):
   app: str = Field(..., description="Nom de l'application à vérifier.")

def smart_recertification_status(app: str) -> str:
   log.info(f"[TOOL] smart_recertification_status: {app}")
   return f"{app} doit être recertifiée avant la fin du mois."

# ---- 4. Tool RAG  ----
embedding_model = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
db = FAISS.load_local("recertification_index", embedding_model)
retriever = db.as_retriever(search_kwargs={"k": 3})

class SmartRagContextArgs(BaseModel):
   query: str = Field(..., description="Texte décrivant ce que l’on cherche dans le contexte de recertification.")

def search_recertification_context(query: str) -> str:
   docs = retriever.invoke(query)
   return "\n\n".join([doc.page_content for doc in docs])

# ---- Enregistrement des tools ----
tools = [
   Tool(name="smart_get_info", func=smart_get_info, args_schema=SmartGetInfoArgs,
        description="Retourne les informations détaillées sur une application à partir de plusieurs critères."),
   Tool(name="smart_audit_access", func=smart_audit_access, args_schema=SmartAuditAccessArgs,
        description="Retourne les derniers accès à une application."),
   Tool(name="smart_recertification_status", func=smart_recertification_status, args_schema=SmartRecertificationStatusArgs,
        description="Indique si une application doit être recertifiée."),
   Tool(name="smart_rag_recertification_context", func=search_recertification_context,
        args_schema=SmartRagContextArgs,
        description="Recherche dans la base RAG les définitions, étapes et acteurs liés à la recertification.")
]

# ============================================================
# 5️⃣ TOOL EXECUTOR LOCAL
# ============================================================
class ToolExecutor:
   def __init__(self, tools):
       self.tool_map = {t.name: t for t in tools}

   def invoke(self, call):
       name = call.get("name")
       args = call.get("arguments", {})
       if name not in self.tool_map:
           raise ValueError(f"Outil non trouvé : {name}")
       tool = self.tool_map[name]
       if isinstance(args, str):
           args = json.loads(args)
       return tool.run(args)

tool_executor = ToolExecutor(tools)

# ============================================================
# 6️⃣ GPT-OSS WRAPPER
# ============================================================
class GPTOSSWrapper:
   def __init__(self, model="openai/gpt-oss-120b"):
       self.model = model

   def invoke(self, messages):
       # Convert LangGraph messages → base_parameters.input
       base_input = []
       for m in messages:
           role = "user" if isinstance(m, HumanMessage) else \
                  "system" if isinstance(m, SystemMessage) else "assistant"
           base_input.append({"role": role, "content": m.content})

       # Extraire le JSON Schema depuis args_schema
       tool_defs = []
       for t in tools:
           schema = t.args_schema.schema() if hasattr(t.args_schema, "schema") else {}
           tool_defs.append({
               "type": "function",
               "name": t.name,
               "description": t.description,
               "parameters": schema
           })

       resp = client.responses.create(
           model=self.model,
           base_parameters={
               "input": base_input,
               "tools": tool_defs,
               "tool_choice": "auto",
               "temperature": 0.1
           }
       )

       try:
           data = json.loads(resp.output_text)
       except Exception as e:
           return AIMessage(content=f"[Erreur parsing modèle] {e}")

       if data.get("analysis"):
           return AIMessage(content=f"[ACTION] {data['analysis']['action']}",
                            additional_kwargs={"analysis": data["analysis"]})
       if data.get("final"):
           return AIMessage(content=data["final"]["answer"])
       return AIMessage(content="Aucune réponse détectée.")

llm_oss = GPTOSSWrapper()

# ============================================================
# 7️⃣ NODES LANGGRAPH
# ============================================================
def assistant(state: MessagesState):
   log.info("[GRAPH] Assistant node triggered.")
   response = llm_oss.invoke(state["messages"])
   return {"messages": [response]}

def tools_node(state: MessagesState):
   last = state["messages"][-1]
   analysis = last.additional_kwargs.get("analysis", {})
   if not analysis:
       return {"messages": [AIMessage(content="Aucun outil appelé.")]}
   name = analysis["action"]
   args = analysis.get("action_input", {})
   log.info(f"[TOOL EXEC] {name}({args})")
   try:
       output = tool_executor.invoke({"name": name, "arguments": args})
       msg = AIMessage(content=f"[Observation] {output}")
   except Exception as e:
       msg = AIMessage(content=f"[Erreur] {e}")
   return {"messages": [msg]}

def stop_condition(state: MessagesState) -> str:
   last = state["messages"][-1]
   if isinstance(last, AIMessage) and "analysis" in last.additional_kwargs:
       return "continue"
   return "end"

# ============================================================
# 8️⃣ GRAPH
# ============================================================
builder = StateGraph(MessagesState)
builder.add_node("assistant", assistant)
builder.add_node("tools", tools_node)
builder.add_node("end", lambda s: s)

builder.add_edge(START, "assistant")
builder.add_conditional_edges("assistant", stop_condition, path_map={"continue": "tools", "end": "end"})
builder.add_conditional_edges("tools", lambda s: "assistant", path_map={"assistant": "assistant"})
builder.set_finish_point("end")
graph = builder.compile()

# ============================================================
# 9️⃣ GRADIO
# ============================================================
chat_history = []

def ask_agent(message: str) -> str:
   global chat_history
   if not chat_history:
       chat_history.append(SystemMessage(content=PROMPT_SYSTEM))
   chat_history.append(HumanMessage(content=message))
   result = graph.invoke({"messages": chat_history})
   msg = result["messages"][-1]
   chat_history.append(msg)
   if isinstance(msg, AIMessage):
       return msg.content
   return "[Erreur] Pas de réponse générée."

demo = gr.Interface(
   fn=ask_agent,
   inputs=gr.Textbox(label="Posez votre question"),
   outputs=gr.Textbox(label="Réponse de l'agent"),
   title="Assistant Recertification – GPT-OSS-120b (args_schema BaseModel)",
   theme="default"
)

if __name__ == "__main__":
   demo.launch(server_name="127.0.0.1", share=False)
 

About Author

Directeur de Publication