Salta el contingut

Plataformes i Frameworks d'Intel·ligència Artificial

Introducció

El 2025, el panorama de les plataformes d'IA es radicalment diferent del que era fa tan sols tres anys. Existeix una proliferacio de models, APIs, frameworks d'orquestracio i eines MLOps que pot ser aclaparadora. Un professional d'IA no ha de coneixer-les totes en profunditat, pero si entendre l'arquitectura general, saber triar la plataforma adequada per a cada cas d'us i ser capac d'integrar-les en una aplicacio real.

Aquest tema cobreix les plataformes mes rellevants de 2025, agrupades en tres categories: APIs comercials, solucions open-source/locals i frameworks d'orquestracio. Al final, analitzem les eines de MLOps per a la gestio del cicle de vida dels models.


1. APIs Comercials de Models de Llenguatge

1.1 OpenAI API

OpenAI es el proveïdor de referencia en LLMs. La seva API es la mes usada del sector i el seu disseny ha marcat l'estandard que segueixen la resta.

Models disponibles el 2025:

Model Parametres Context Punt Fort Cost (per 1M tokens)
GPT-4o ~200B 128K Millor rendiment general, multimodal $5 entrada / $15 sortida
GPT-4o-mini ~8B 128K Rapid i economi c $0.15 entrada / $0.60 sortida
o1 ~? 128K Raonament complex, matemàtiques $15 entrada / $60 sortida
o1-mini ~? 128K Raonament economi c $3 entrada / $12 sortida
text-embedding-3-small 8K Embeddings eficients $0.02
text-embedding-3-large 8K Embeddings d'alta qualitat $0.13
dall-e-3 Generacio d'imatges $0.04-0.12 per imatge
whisper-1 Transcripcio d'audio $0.006 per minut

Exemple complet amb OpenAI API:

from openai import OpenAI
from pydantic import BaseModel
import os

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

# 1. Chat basic
resposta = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Ets un assistent tecnic expert en IA. Respon en catala."},
        {"role": "user", "content": "Explica que es RAG en 3 linies."}
    ],
    temperature=0.7,
    max_tokens=200
)
print(resposta.choices[0].message.content)
print(f"Tokens usats: {resposta.usage.total_tokens}")

# 2. Structured Output (JSON mode)
class AnalisiTecnica(BaseModel):
    tecnologia: str
    pros: list[str]
    cons: list[str]
    cas_us_recomanat: str

resposta_json = client.beta.chat.completions.parse(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Analitza tecnologies d'IA i retorna JSON estructurat."},
        {"role": "user", "content": "Analitza l'us de RAG per a una empresa de serveis juridics."}
    ],
    response_format=AnalisiTecnica
)
analisi = resposta_json.choices[0].message.parsed
print(f"Tecnologia: {analisi.tecnologia}")
print(f"Pros: {analisi.pros}")

# 3. Function calling / Tool use
tools = [
    {
        "type": "function",
        "function": {
            "name": "cercar_preu_accio",
            "description": "Cerca el preu actual d'una accio de borsa",
            "parameters": {
                "type": "object",
                "properties": {
                    "ticker": {
                        "type": "string",
                        "description": "Simbol de l'accio (ex: AAPL, NVDA)"
                    }
                },
                "required": ["ticker"]
            }
        }
    }
]

resposta_tool = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Quin es el preu d'NVIDIA avui?"}],
    tools=tools,
    tool_choice="auto"
)

# Comprovar si el model vol usar una tool
if resposta_tool.choices[0].message.tool_calls:
    tool_call = resposta_tool.choices[0].message.tool_calls[0]
    print(f"El model vol cridar: {tool_call.function.name}")
    print(f"Amb arguments: {tool_call.function.arguments}")

# 4. Embeddings
textos = ["Python per a IA", "Machine Learning amb scikit-learn", "LangChain per a agents"]
embeddings = client.embeddings.create(
    model="text-embedding-3-small",
    input=textos
)
vectors = [e.embedding for e in embeddings.data]
print(f"Dimensio dels embeddings: {len(vectors[0])}")  # 1536

# 5. Visio (GPT-4o)
import base64
from pathlib import Path

def codificar_imatge(path: str) -> str:
    with open(path, "rb") as f:
        return base64.b64encode(f.read()).decode("utf-8")

resposta_visio = client.chat.completions.create(
    model="gpt-4o",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Descriu aquesta imatge en catala:"},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{codificar_imatge('diagrama.jpg')}",
                        "detail": "high"
                    }
                }
            ]
        }
    ],
    max_tokens=300
)

Bones pràctiques amb l'API d'OpenAI

  • Usa sempre gpt-4o-mini per a prototips i proves; reserva gpt-4o per a produccio quan la qualitat ho justifiqui.
  • Implementa retry amb backoff exponencial per gestionar errors 429 (rate limit).
  • Usa streaming (stream=True) per a aplicacions de chat per millorar la percep cio de velocitat.
  • Guarda les claus API sempre en variables d'entorn, mai al codi.

1.2 Anthropic API — Claude

Anthropic, fundada el 2021 per ex-membres d'OpenAI (incloent Dario i Daniela Amodei), va crear Claude amb un enfocament en la seguretat i l'alineament (Constitutional AI). El 2025, Claude 3.5 Sonnet es un dels millors models per a tasques de codi i analisi de documents llargs.

Models Claude el 2025:

Model Context Punt Fort Cost (per 1M tokens)
Claude 3.5 Sonnet 200K Millor en codi, analisi, escriptura $3 entrada / $15 sortida
Claude 3.5 Haiku 200K Rapid i economi c $0.80 entrada / $4 sortida
Claude 3 Opus 200K Raonament complex $15 entrada / $75 sortida

La finestra de 200K tokens de Claude es especialment util per a analisi de documents llargs (contractes, informes, codi base gran).

import anthropic
import os

client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

# 1. Chat basic amb Claude 3.5 Sonnet
missatge = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    system="Ets un expert en programacio Python i IA. Respon sempre en catala.",
    messages=[
        {"role": "user", "content": "Escriu una funcio Python per calcular la similitud cosinus entre dos vectors."}
    ]
)
print(missatge.content[0].text)
print(f"Input tokens: {missatge.usage.input_tokens}")
print(f"Output tokens: {missatge.usage.output_tokens}")

# 2. Analisi de document llarg (gracies als 200K tokens de context)
document_llarg = Path("informe_anual.txt").read_text(encoding="utf-8")

analisi = client.messages.create(
    model="claude-3-5-haiku-20241022",  # Haiku per a tasques rapids
    max_tokens=2048,
    messages=[
        {
            "role": "user",
            "content": f"""Analitza aquest informe anual i extreu:
            1. Els 5 punts mes importants
            2. Les principals amenaçes identificades
            3. Les oportunitats de creixement

            INFORME:
            {document_llarg}"""
        }
    ]
)

# 3. Streaming
with client.messages.stream(
    model="claude-3-5-sonnet-20241022",
    max_tokens=500,
    messages=[{"role": "user", "content": "Explica el teorema de Bayes i la seva aplicacio en ML."}]
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

# 4. Tool use amb Claude
tools_claude = [
    {
        "name": "calcular_cost_api",
        "description": "Calcula el cost d'una crida a una API d'IA",
        "input_schema": {
            "type": "object",
            "properties": {
                "model": {"type": "string", "description": "Nom del model"},
                "tokens_entrada": {"type": "integer"},
                "tokens_sortida": {"type": "integer"}
            },
            "required": ["model", "tokens_entrada", "tokens_sortida"]
        }
    }
]

resposta_tool = client.messages.create(
    model="claude-3-5-haiku-20241022",
    max_tokens=300,
    tools=tools_claude,
    messages=[{
        "role": "user",
        "content": "Quant costaria analitzar 1000 documents de 500 tokens cada un amb Claude 3.5 Haiku?"
    }]
)

1.3 Google AI: Gemini

Google va llançar la familia Gemini el desembre de 2023, i el 2025 ofereix models molt competitius, especialment per a tasques multimodals i per a finestres de context extremadament llargues.

Models Gemini el 2025:

Model Context Punt Fort
Gemini 1.5 Pro 2M tokens Finestra de context enorme, video, audio
Gemini 1.5 Flash 1M tokens Rapid i economi c
Gemini 2.0 Flash 1M tokens El mes recent, multimodal natiu
text-embedding-004 8K Embeddings multilinguals
import google.generativeai as genai
import os

genai.configure(api_key=os.environ["GOOGLE_API_KEY"])

# Usar Gemini 1.5 Flash (economi c i rapid)
model = genai.GenerativeModel(
    "gemini-1.5-flash",
    system_instruction="Ets un assistent d'IA especialitzat. Respon en catala."
)

resposta = model.generate_content(
    "Quins son els avantatges de LangGraph sobre LangChain Classic?",
    generation_config=genai.types.GenerationConfig(
        temperature=0.7,
        max_output_tokens=500
    )
)
print(resposta.text)

# Finestra de context enorme: analitzar un video o audio llarg
# (Unic a Gemini 1.5 Pro/Flash)
import PIL.Image

imatge = PIL.Image.open("diagrama_arquitectura.png")
resposta_multimodal = model.generate_content([
    "Descriu i analitza aquesta arquitectura de sistema en catala:",
    imatge
])
print(resposta_multimodal.text)

Vertex AI es la plataforma empresarial de Google Cloud per a IA. Ofereix els mateixos models Gemini pero amb SLAs empresarials, VPCSC, auditoria i integracio amb l'ecosistema GCP.

1.4 AWS Bedrock

AWS Bedrock es la plataforma gestionada d'Amazon per accedir a models de tercers sense gestionar infraestructura. El 2025, ofereix acces a:

  • Anthropic Claude (3.5 Sonnet, 3 Haiku)
  • Meta Llama 3 (8B, 70B, 405B)
  • Mistral (7B, 8x7B Mixture of Experts)
  • Amazon Titan (models propis d'AWS)
  • Stability AI (SDXL per a imatges)

L'avantatge de Bedrock es la integra cio nativa amb AWS (IAM, CloudWatch, VPC, S3) i el compliment normatiu (SOC 2, HIPAA, ISO 27001).

import boto3
import json

bedrock = boto3.client(
    service_name="bedrock-runtime",
    region_name="eu-west-1"  # disponible a Europa
)

# Cridar Claude 3.5 Haiku via Bedrock
cos_peticio = json.dumps({
    "anthropic_version": "bedrock-2023-05-31",
    "max_tokens": 500,
    "messages": [
        {"role": "user", "content": "Que es el federated learning? Respon en catala."}
    ]
})

resposta = bedrock.invoke_model(
    body=cos_peticio,
    modelId="anthropic.claude-3-5-haiku-20241022-v1:0",
    accept="application/json",
    contentType="application/json"
)

cos_resposta = json.loads(resposta.get("body").read())
print(cos_resposta["content"][0]["text"])

1.5 Azure AI Foundry

Azure AI Foundry (successor d'Azure OpenAI Service) es la plataforma empresarial de Microsoft per a IA. Ofereix acces a models GPT-4o, Claude, Llama i Mistral amb:

  • Xarxa privada: els models s'executen en la teva subscripcio Azure
  • Data residency: les dades no surten de la teva regio
  • SLA del 99.9% per a produccio
  • Integracio amb Microsoft 365 (Copilot Studio)

Es la plataforma preferida per a grans empreses europees que necesiten compliment GDPR estricte.

1.6 Mistral AI

Mistral AI, empresa francesa fundada el 2023, ha demostrat que es possible competir amb OpenAI amb models mes petits i eficients. La seva filosofia open-source parcial i la seva localitzacio europea la fan especialment interessant per a empreses que volen sobirania de dades.

Model Parametres Punt Fort
Mistral Large ~70B Millor model de Mistral, codi i multilingue
Mistral Small ~22B Balanc rendiment/preu
Mistral 7B 7B Altament eficient, disponible open-source
Mixtral 8x7B 56B (MoE) Mixture of Experts, molt eficient
Codestral ~22B Especialitzat en generacio de codi
from mistralai import Mistral
import os

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

resposta = client.chat.complete(
    model="mistral-small-latest",
    messages=[
        {"role": "user", "content": "Explica la diferencia entre MoE i dense transformers."}
    ]
)
print(resposta.choices[0].message.content)

2. Plataformes Open Source i Models Locals

2.1 Ollama: models locals amb facilitat

Ollama es la manera mes senzilla d'executar LLMs de manera local, sense cost d'API i amb privacitat total. El 2025, suporta desenes de models.

Instal·lació i primers passos:

# Instal·lar Ollama (Linux/Mac)
curl -fsSL https://ollama.com/install.sh | sh

# O via Docker (recomanat per a practiques)
docker run -d \
  --name ollama-joan-garcia \
  -p 11434:11434 \
  -v ollama-data:/root/.ollama \
  ollama/ollama

# Descarregar models
docker exec ollama-joan-garcia ollama pull llama3.1        # 8B, ~4.7GB
docker exec ollama-joan-garcia ollama pull mistral         # 7B, ~4.1GB
docker exec ollama-joan-garcia ollama pull phi3            # 3.8B, ~2.3GB (petit pero potent)
docker exec ollama-joan-garcia ollama pull gemma2          # 9B, ~5.5GB
docker exec ollama-joan-garcia ollama pull llama3.1:70b   # 70B, necessita 40GB RAM

# Llistar models descarregats
docker exec ollama-joan-garcia ollama list

# Xatejar directament
docker exec -it ollama-joan-garcia ollama run llama3.1 "Que es RAG?"

Integrar Ollama amb Python:

import ollama
import requests
import json

# 1. Via biblioteca oficial ollama-python
client = ollama.Client(host="http://localhost:11434")

resposta = client.chat(
    model="llama3.1",
    messages=[
        {"role": "system", "content": "Ets un assistent tecnic. Respon en catala."},
        {"role": "user", "content": "Quina diferencia hi ha entre LangChain i LlamaIndex?"}
    ]
)
print(resposta["message"]["content"])

# 2. Streaming
for chunk in client.chat(
    model="llama3.1",
    messages=[{"role": "user", "content": "Explica els transformers pas a pas."}],
    stream=True
):
    print(chunk["message"]["content"], end="", flush=True)

# 3. Embeddings amb Ollama
embedding = client.embeddings(
    model="nomic-embed-text",  # model d'embeddings petit i rapid
    prompt="Intel·ligencia Artificial i Big Data"
)
print(f"Dimensio: {len(embedding['embedding'])}")  # 768

# 4. Via API REST (compatible amb OpenAI)
# Aixo permet usar Ollama com a drop-in replacement d'OpenAI!
from openai import OpenAI

client_openai = OpenAI(
    base_url="http://localhost:11434/v1",
    api_key="ollama"  # Ollama no necessita clau real
)

resposta = client_openai.chat.completions.create(
    model="llama3.1",
    messages=[{"role": "user", "content": "Hola! Qui ets?"}]
)
print(resposta.choices[0].message.content)

Models Ollama recomanats per a les practiques:

Model Mida RAM Necessaria Cas d'us
llama3.1 4.7 GB 8 GB Us general, codi, catala acceptable
mistral 4.1 GB 8 GB Instruccions, codi, rapid
phi3 2.3 GB 6 GB Dispositius limitats, sorprenentment bo
gemma2 5.5 GB 10 GB Google, multilingue
nomic-embed-text 274 MB 2 GB Embeddings (per a RAG)
llava 4.7 GB 8 GB Visio (analisi d'imatges)

Requisits de maquinari per a Ollama

Per executar models de 7-8B parametres de manera fluid, necessites almenys 8 GB de RAM. Per a models de 70B, necessites 40+ GB. Si el teu ordinador te menys de 8 GB, usa phi3 (3.8B) que ofereix bon rendiment amb pocs recursos. Alternativament, usa Google Colab o les APIs comercials.

Miniactivitat

Desplega Ollama amb Docker i descarrega el model phi3 o llama3.1. Escriu un script Python que faci 5 preguntes sobre el curs IABD i guardi les respostes en un fitxer JSON. Usa el client openai apuntant a http://localhost:11434/v1 per demostrar la compatibilitat.

2.2 LM Studio: interfície gràfica per a models locals

LM Studio ofereix una interficie grafica d'escriptori per descarregar i executar models GGUF localment. Es la manera mes facil per a usuaris que prefereixen GUI sobre CLI:

  • Descarrega models directament des de Hugging Face Hub
  • Interficie de chat integrada
  • Servidor local compatible amb OpenAI API
  • Suporta Apple Silicon (Metal GPU), NVIDIA (CUDA) i CPU

Es especialment util per a demostracions i per a alumnat que vol experimentar amb models sense Docker.

2.3 Hugging Face Transformers

El Hub de Hugging Face allotja mes de 500.000 models (febrer 2025). La biblioteca transformers permet usar-los amb una API unificada:

from transformers import (
    pipeline,
    AutoTokenizer,
    AutoModelForCausalLM,
    BitsAndBytesConfig
)
import torch

# 1. Pipeline d'alt nivell (la manera mes senzilla)
generador = pipeline(
    "text-generation",
    model="mistralai/Mistral-7B-Instruct-v0.3",
    device_map="auto",  # Distribueix automàticament entre CPU/GPU
    torch_dtype=torch.bfloat16  # Quantitzacio per estalviar memoria
)

resultat = generador(
    "<s>[INST] Explica que es un vector store en el context del RAG. [/INST]",
    max_new_tokens=300,
    do_sample=True,
    temperature=0.7
)
print(resultat[0]["generated_text"])

# 2. Quantitzacio de 4 bits per a models grans en GPU limitada
config_quant = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True
)

# Carrega un model de 7B en ~4GB GPU VRAM (en lloc dels 14GB normals)
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-Instruct-v0.3")
model = AutoModelForCausalLM.from_pretrained(
    "mistralai/Mistral-7B-Instruct-v0.3",
    quantization_config=config_quant,
    device_map="auto"
)

# 3. Tasques especifiques: embeddings multilinguals
from sentence_transformers import SentenceTransformer
import numpy as np

embed_model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-mpnet-base-v2")

# Calcul de similitud semantica en catala
frases = [
    "El gat esta sobre la taula.",
    "El felí reposa damunt el moble.",
    "El gos juga al jardí."
]

embeddings = embed_model.encode(frases, normalize_embeddings=True)

# Matriu de similituds
similituds = embeddings @ embeddings.T
print("Matriu de similituds:")
for i, frase in enumerate(frases):
    for j, frase2 in enumerate(frases):
        print(f"  {i+1}-{j+1}: {similituds[i,j]:.3f}")

3. Frameworks d'Orquestració

3.1 LangChain: el marc dominant per a LLMs

LangChain (versio 0.3+) ha evolucionat molt des del seu llançament el 2022. La versio actual adopta LCEL (LangChain Expression Language), que usa el patró pipe (|) per compondre cadenes:

from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_anthropic import ChatAnthropic
from langchain_community.llms import Ollama
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_community.vectorstores import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader, WebBaseLoader

# 1. Cadena senzilla
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

prompt = ChatPromptTemplate.from_messages([
    ("system", "Ets un expert tecnic en IA. Respon sempre en catala i de manera concisa."),
    ("human", "{pregunta}")
])

# LCEL: els components es combinen amb |
cadena_basica = prompt | llm | StrOutputParser()
print(cadena_basica.invoke({"pregunta": "Que es LangGraph?"}))

# 2. Cadena amb memoria
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

historial = InMemoryChatMessageHistory()

cadena_amb_historial = RunnableWithMessageHistory(
    cadena_basica,
    lambda session_id: historial,
    input_messages_key="pregunta",
    history_messages_key="historial"
)

# 3. Pipeline RAG complet
def crear_rag_complet(pdf_path: str) -> any:
    """Construeix un sistema RAG des d'un PDF."""

    # Carregar i dividir document
    loader = PyPDFLoader(pdf_path)
    documents = loader.load()

    splitter = RecursiveCharacterTextSplitter(
        chunk_size=1000,
        chunk_overlap=200,
        separators=["\n\n", "\n", ".", "!", "?", ",", " "]
    )
    chunks = splitter.split_documents(documents)
    print(f"Document dividit en {len(chunks)} chunks")

    # Crear embeddings i vector store
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    vectorstore = Chroma.from_documents(
        documents=chunks,
        embedding=embeddings,
        collection_name="rag-documents",
        persist_directory="./chroma_db"
    )

    retriever = vectorstore.as_retriever(
        search_type="mmr",  # Maximum Marginal Relevance (mes diversitat)
        search_kwargs={"k": 5, "fetch_k": 20}
    )

    # Prompt RAG
    prompt_rag = ChatPromptTemplate.from_messages([
        ("system", """Ets un assistent expert. Usa EXCLUSIVAMENT la informacio del context per respondre.
        Si la informacio no es al context, indica-ho clarament.

        CONTEXT:
        {context}

        Respon sempre en catala."""),
        ("human", "{pregunta}")
    ])

    def format_docs(docs):
        return "\n\n---\n\n".join(
            f"[Font: {doc.metadata.get('source', 'desconeguda')}, "
            f"Pagina: {doc.metadata.get('page', '?')}]\n{doc.page_content}"
            for doc in docs
        )

    # Cadena RAG amb LCEL
    cadena_rag = (
        {
            "context": retriever | format_docs,
            "pregunta": RunnablePassthrough()
        }
        | prompt_rag
        | ChatOpenAI(model="gpt-4o-mini")
        | StrOutputParser()
    )

    return cadena_rag

3.2 LangGraph: agents amb estat

LangGraph (versio 0.2+) permet construir agents com a grafs d'estat. Es mes potent que les cadenes lineals de LangChain per a workflows complexos:

from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from typing import TypedDict, Annotated
import operator

# Definir l'estat de l'agent
class EstatAgent(TypedDict):
    missatges: Annotated[list, operator.add]  # els missatges s'acumulen
    iteracio_actual: int
    resultat_final: str

# Definir tools
@tool
def calcular(expressio: str) -> str:
    """Calcula una expressio matematica. Exemple: '2 + 3 * 4'"""
    try:
        resultat = eval(expressio)  # en produccio, usar ast.literal_eval
        return f"Resultat de '{expressio}' = {resultat}"
    except Exception as e:
        return f"Error: {e}"

@tool
def cercar_informacio(consulta: str) -> str:
    """Cerca informacio sobre un tema. (Demo: retorna text static)"""
    # En produccio, integraria Tavily, Serper o similar
    return f"Informacio trobada sobre '{consulta}': [Resultat de la cerca simulada]"

# LLM amb tools
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [calcular, cercar_informacio]
llm_amb_tools = llm.bind_tools(tools)

# Nodes del graf
def node_agent(estat: EstatAgent) -> dict:
    """Node principal: crida al LLM."""
    resposta = llm_amb_tools.invoke(estat["missatges"])
    return {"missatges": [resposta]}

# Construir el graf
builder = StateGraph(EstatAgent)

builder.add_node("agent", node_agent)
builder.add_node("tools", ToolNode(tools))

builder.add_edge(START, "agent")
builder.add_conditional_edges(
    "agent",
    tools_condition,  # Si el model vol usar una tool -> "tools"; sino -> END
    {"tools": "tools", END: END}
)
builder.add_edge("tools", "agent")  # Despres de la tool, torna a l'agent

agent_compilat = builder.compile()

# Executar l'agent
resultat = agent_compilat.invoke({
    "missatges": [
        SystemMessage(content="Ets un assistent matematich. Usa les tools disponibles."),
        HumanMessage(content="Calcula 15 * 23 + 47 i explica el resultat.")
    ],
    "iteracio_actual": 0,
    "resultat_final": ""
})

print(resultat["missatges"][-1].content)

3.3 LlamaIndex: RAG avançat

LlamaIndex esta especialitzat en RAG i indexacio de dades per a LLMs. Ofereix mes opcions que LangChain per a casos d'us complexos:

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core.node_parser import SentenceSplitter

# Configuracio global
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.node_parser = SentenceSplitter(chunk_size=512, chunk_overlap=50)

# Carregar documents i crear index
documents = SimpleDirectoryReader("./documents").load_data()
index = VectorStoreIndex.from_documents(documents)

# Query engine
query_engine = index.as_query_engine(
    similarity_top_k=5,
    response_mode="tree_summarize"  # Millor per a preguntes sobre documents llargs
)

resposta = query_engine.query("Quines son les principals limitacions dels LLMs actuals?")
print(resposta.response)
print("\nFonts:")
for node in resposta.source_nodes:
    print(f"  - {node.metadata.get('file_name', 'desconegut')}: score={node.score:.3f}")

3.4 CrewAI: sistemes multi-agent

CrewAI permet definir equips d'agents especialitzats que col·laboren per resoldre tasques complexes:

from crewai import Agent, Task, Crew, Process
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")

# Definir agents especialitzats
investigador = Agent(
    role="Investigador d'IA",
    goal="Investigar i recopilar informacio actualitzada sobre temes d'IA",
    backstory="""Ets un investigador expert en IA amb acces a les ultimes publicacions.
    Sempre cites les fonts i valides la informacio.""",
    llm=llm,
    verbose=True
)

redactor = Agent(
    role="Redactor Tecnic",
    goal="Redactar articles tecnics clars i precisos en catala",
    backstory="""Ets un redactor tecnic expert que transforma informacio complexa
    en contingut accessible per a professionals del sector.""",
    llm=llm,
    verbose=True
)

# Definir tasques
tasca_investigar = Task(
    description="Investiga l'estat actual dels models multimodals el 2025: GPT-4o, Gemini 1.5, LLaVA",
    expected_output="Informe de recerca amb comparativa de capacitats i casos d'us",
    agent=investigador
)

tasca_redactar = Task(
    description="Redacta un article de 500 paraules en catala sobre els models multimodals del 2025",
    expected_output="Article complet en catala, formatat en Markdown",
    agent=redactor,
    context=[tasca_investigar]  # Depe n de la tasca anterior
)

# Crear i executar el crew
crew = Crew(
    agents=[investigador, redactor],
    tasks=[tasca_investigar, tasca_redactar],
    process=Process.sequential,
    verbose=True
)

resultat = crew.kickoff()
print(resultat.raw)

4. MLOps: Cicle de vida dels models

4.1 MLflow: experiment tracking

MLflow es l'eina open-source estandard per a tracking d'experiments de ML:

import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, f1_score
import pandas as pd

# Configurar servidor MLflow (local o remot)
mlflow.set_tracking_uri("http://localhost:5000")
mlflow.set_experiment("classificador-sentiment-5073")

with mlflow.start_run(run_name="rf-baseline"):
    # Parametres
    n_estimators = 200
    max_depth = 10
    mlflow.log_params({
        "model": "RandomForestClassifier",
        "n_estimators": n_estimators,
        "max_depth": max_depth,
        "features": "tfidf-5000"
    })

    # Entrenar
    clf = RandomForestClassifier(n_estimators=n_estimators, max_depth=max_depth)
    clf.fit(X_train, y_train)

    # Metriques
    y_pred = clf.predict(X_test)
    accuracy = accuracy_score(y_test, y_pred)
    f1 = f1_score(y_test, y_pred, average='weighted')

    mlflow.log_metrics({"accuracy": accuracy, "f1_weighted": f1})

    # Guardar el model
    mlflow.sklearn.log_model(clf, "model", registered_model_name="sentiment-classifier")

    print(f"Accuracy: {accuracy:.4f}, F1: {f1:.4f}")
    print(f"Run ID: {mlflow.active_run().info.run_id}")
# Desplegar MLflow amb Docker
docker run -d \
  --name mlflow-joan-garcia \
  -p 5000:5000 \
  -v $(pwd)/mlruns:/mlruns \
  ghcr.io/mlflow/mlflow:latest \
  mlflow server \
    --host 0.0.0.0 \
    --port 5000 \
    --backend-store-uri sqlite:///mlruns/mlflow.db \
    --default-artifact-root /mlruns/artifacts

4.2 Weights & Biases (W&B)

W&B ofereix visualitzacio d'experiments mes rica que MLflow, especialment per a deep learning:

import wandb
import torch

wandb.init(
    project="modul-5073-dl",
    name="transformer-sentiment-v1",
    config={
        "learning_rate": 2e-5,
        "epochs": 10,
        "batch_size": 32,
        "model": "bert-base-multilingual-cased",
        "dataset": "twitter-catala-sentiment"
    }
)

# Durant l'entrenament
for epoch in range(config.epochs):
    train_loss = entrenar_epoch(model, train_loader, optimizer)
    val_metrics = avaluar(model, val_loader)

    wandb.log({
        "epoch": epoch,
        "train/loss": train_loss,
        "val/accuracy": val_metrics["accuracy"],
        "val/f1": val_metrics["f1"],
        "lr": optimizer.param_groups[0]["lr"]
    })

    # Guardar el millor model
    if val_metrics["f1"] > best_f1:
        wandb.save("model_best.pt")

wandb.finish()

5. Comparativa de costos i casos d'ús

graph TD
    PROBLEMA["Problema d'IA"] --> Q1{"Necessita privacitat\ntotal de les dades?"}
    Q1 -->|"Si"| LOCAL["Models Locals\n(Ollama + LLaMA 3.1)"]
    Q1 -->|"No"| Q2{"Quin es el\nvolum de peticions?"}

    Q2 -->|"Alt (>1M/mes)"| Q3{"Prioritat?"}
    Q2 -->|"Baix (<100K/mes)"| API_COM["API Comercial\n(OpenAI, Anthropic)"]

    Q3 -->|"Cost"| BEDROCK["AWS Bedrock\nor Azure AI Foundry"]
    Q3 -->|"Rendiment"| GPT4O["OpenAI GPT-4o\nor Claude 3.5 Sonnet"]

    API_COM --> Q4{"Tasca complexa\no senzilla?"}
    Q4 -->|"Senzilla"| MINI["GPT-4o-mini\nor Claude Haiku"]
    Q4 -->|"Complexa"| POTENT["GPT-4o\nor Claude Sonnet"]
Cas d'us Plataforma recomanada Motiu
Prototipat rapid Ollama + LLaMA 3.1 Gratu it, rapid d'iniciar
Produccio de baix cost GPT-4o-mini o Claude Haiku Equilibri preu/qualitat
Analisi de documents llargs Claude 3.5 Sonnet (200K ctx) Context enorm
Tasques de codi Claude 3.5 Sonnet o GPT-4o Millors en codi
Multimodal (imatges/audio) GPT-4o o Gemini 1.5 Natius multimodals
Empresa amb GDPR estricte Azure AI Foundry Dades a Europa, SLA
Recerca amb GPU local Hugging Face + PyTorch Control total
MLOps a escala AWS SageMaker + Bedrock Integracio AWS

Exercici pràctic

AC5073/01-1 — Comparativa de plataformes

Implementa un script Python que faci la mateixa pregunta tecnica a tres plataformes diferentes (OpenAI GPT-4o-mini, Claude 3.5 Haiku i Ollama LLaMA 3.1) i compari:

  1. Qualitat de la resposta (valoracio subjectiva 1-5)
  2. Temps de resposta (mesurat amb time.perf_counter())
  3. Cost estimat (calculat a partir dels tokens usats)
  4. Format de la resposta (clar, estructurat, amb exemples?)

Entrega un informe en Markdown amb els resultats i les teves conclusions sobre quin model triaries per a cada cas d'us.

Preguntes de reflexio

  1. Per quin motiu Azure AI Foundry es preferible a OpenAI directament per a una empresa bancaria europea?
  2. Quins son els avantatges i desavantatges d'usar Ollama respecte a les APIs comercials en un entorn de produccio?
  3. Quan triaries LlamaIndex per sobre de LangChain per a un projecte RAG?
  4. Que es el "vendor lock-in" en el context de les APIs d'IA i com es pot mitigar?