Salta el contingut

2.2 Components d'un Agent en Detall

Objectiu

Analitzar en profunditat cada component d'un agent: el LLM com a motor de raonament, el sistema de prompts, la integració d'eines, i l'executor que ho coordina tot.


🧠 Component 1: El Motor de Raonament (LLM)

El System Prompt

El system prompt defineix la "personalitat" i capacitats de l'agent:

SYSTEM_PROMPT = """
Ets un assistent tècnic expert en administració de sistemes (ASIX).

CAPACITATS:
- Pots buscar informació tècnica actualitzada a internet
- Pots executar comandes de diagnòstic
- Pots llegir i analitzar fitxers de log

LIMITACIONS:
- No executes mai comandes destructives sense confirmació
- Si no tens certesa, dius "No ho sé" en lloc d'inventar

FORMAT: Respostes en català. Scripts en blocs de codi.
"""

from langchain.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
    ("system", SYSTEM_PROMPT),
    ("placeholder", "{chat_history}"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

Temperatura i Paràmetres

from langchain_openai import ChatOpenAI

# Agents → temperatura 0 (determinista, segueix instruccions de format)
llm_agent = ChatOpenAI(model="gpt-4o", temperature=0, max_tokens=2000)

# Generació creativa → temperatura alta
llm_creatiu = ChatOpenAI(model="gpt-4o", temperature=0.8)

🔧 Component 2: Les Eines (Tools)

Definir Eines amb Pydantic (recomanat)

from langchain.tools import BaseTool
from pydantic import BaseModel, Field
from typing import Optional, Type

class ConsultaBDInput(BaseModel):
    taula: str = Field(description="Taula: alumnes, notes, assignatures, grups")
    condicio: Optional[str] = Field(default=None, description="Condició WHERE SQL")
    limit: int = Field(default=10, ge=1, le=100)

class ConsultaBDTool(BaseTool):
    name: str = "consulta_bd_alumnes"
    description: str = (
        "Consulta la base de dades d'alumnes. "
        "ÚNICAMENT per a lectura, mai per a modificar dades."
    )
    args_schema: Type[BaseModel] = ConsultaBDInput

    def _run(self, taula: str, condicio: Optional[str] = None, limit: int = 10) -> str:
        import sqlite3
        taules_permeses = {"alumnes", "notes", "assignatures", "grups"}
        if taula not in taules_permeses:
            return f"Error: taula '{taula}' no accessible."
        try:
            conn = sqlite3.connect("institut.db")
            cursor = conn.cursor()
            query = f"SELECT * FROM {taula}"
            if condicio:
                query += f" WHERE {condicio}"
            query += f" LIMIT {limit}"
            cursor.execute(query)
            rows = cursor.fetchall()
            if not rows:
                return "No s'han trobat resultats."
            columns = [d[0] for d in cursor.description]
            lines = [", ".join(columns)] + [", ".join(str(v) for v in r) for r in rows]
            return f"{len(rows)} files:\n" + "\n".join(lines)
        except sqlite3.Error as e:
            return f"Error BD: {e}"
        finally:
            conn.close()

    async def _arun(self, *args, **kwargs):
        raise NotImplementedError

Eines Pre-construïdes de LangChain

# pip install langchain-community
from langchain_community.tools import DuckDuckGoSearchResults, WikipediaQueryRun
from langchain_community.agent_toolkits import FileManagementToolkit

# Eines de fitxers (confinades a un directori segur)
file_tools = FileManagementToolkit(
    root_dir="/tmp/agent_workspace",
    selected_tools=["read_file", "write_file", "list_directory"]
).get_tools()

🔄 Component 3: L'Executor

from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_openai import ChatOpenAI

llm   = ChatOpenAI(model="gpt-4o", temperature=0)
agent = create_tool_calling_agent(llm, tools, prompt)

executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=10,
    max_execution_time=60,
    handle_parsing_errors=True,
    return_intermediate_steps=True,
)

# Conversa multi-torn
from langchain_core.messages import HumanMessage, AIMessage

chat_history = []

def chat(message: str) -> str:
    result = executor.invoke({"input": message, "chat_history": chat_history})
    chat_history.extend([
        HumanMessage(content=message),
        AIMessage(content=result["output"])
    ])
    return result["output"]

print(chat("Quin és el port per defecte de SSH?"))
print(chat("I com el canviaria a 2222?"))  # Recorda el context!

📊 El Context en Cada Iteració

┌──────────────────────────────────────────┐
│  [SYSTEM PROMPT]    ~500-2000 tokens     │
│  [HISTORIAL]        creix amb el temps   │
│  [SCRATCHPAD]       Thought/Action/Obs   │
│  [MISSATGE ACTUAL]  la pregunta nova     │
└──────────────────────────────────────────┘

✅ Activitats

Exercici 2.2.1 — System Prompt per ASIX

Crea un system prompt per a un agent d'administració de xarxa. Ha d'incloure: rol, capacitats, limitacions de seguretat, format de resposta, i instruccions per quan no sap la resposta.

Exercici 2.2.2 — Eina ping_host

Implementa una eina que executi ping real a un host, retorni el temps de resposta, i gestioni errors (host inexistent, timeout). Integra-la en un agent.

Exercici 2.2.3 — Anàlisi del Context

Amb return_intermediate_steps=True, per a una tasca de 5+ iteracions, calcula quants tokens consumeix cada part del context per iteració.