Vai al contenuto principale
OpenData AI — progetto sperimentale

← Documentazione

Microsoft Agent Framework (MAF)

Il backend di OpenData AI usa già il agent-framework Microsoft sotto al cofano. Questo significa che puoi costruire un agente locale che riusa esattamente gli stessi MCP server (CKAN, ISTAT, OSM) con poche righe di codice.

Setup

# agent-framework è pubblicato come pre-release: serve --pre
pip install --pre agent-framework anthropic

# Avvia i tre MCP server in altre shell (vedi /docs/mcp):
#   TRANSPORT=streamable-http PORT=8080 ckan-mcp
#   TRANSPORT=streamable-http PORT=8081 istat-mcp
#   TRANSPORT=streamable-http PORT=8082 osm-mcp

Agente con un singolo MCP (CKAN)

Il pattern minimo: un ChatAgent che usa Claude come modello e MCPStreamableHTTPTool per collegarsi al server CKAN.

import asyncio
from agent_framework import ChatAgent
from agent_framework.anthropic import AnthropicChatClient
from agent_framework.mcp import MCPStreamableHTTPTool

CKAN_MCP_URL = "http://localhost:8080/mcp"
SYSTEM = (
    "Sei un assistente specializzato nei portali CKAN italiani. "
    "Usa il tool package_search per cercare dataset su dati.gov.it. "
    "Rispondi in italiano con un elenco numerato delle risorse trovate."
)

async def main() -> None:
    async with MCPStreamableHTTPTool(name="ckan", url=CKAN_MCP_URL) as ckan:
        agent = ChatAgent(
            chat_client=AnthropicChatClient(model="claude-haiku-4-5"),
            instructions=SYSTEM,
            tools=[ckan],
        )
        reply = await agent.run("Cerca dataset sulla qualità dell'aria a Milano")
        print(reply.text)

asyncio.run(main())

Multi-MCP: CKAN + ISTAT + OSM

Stesso pattern, più tool. L'agente decide a quale server chiedere in base al system prompt e al contenuto della domanda.

import asyncio
from contextlib import AsyncExitStack
from agent_framework import ChatAgent
from agent_framework.anthropic import AnthropicChatClient
from agent_framework.mcp import MCPStreamableHTTPTool

MCPS = {
    "ckan":  "http://localhost:8080/mcp",
    "istat": "http://localhost:8081/mcp",
    "osm":   "http://localhost:8082/mcp",
}

async def main() -> None:
    async with AsyncExitStack() as stack:
        tools = [
            await stack.enter_async_context(
                MCPStreamableHTTPTool(name=name, url=url)
            )
            for name, url in MCPS.items()
        ]
        agent = ChatAgent(
            chat_client=AnthropicChatClient(model="claude-sonnet-4-6"),
            instructions=(
                "Sei un agente per open data italiani ed europei. "
                "Usa il tool 'ckan' per portali CKAN, 'istat' per SDMX "
                "(ISTAT/Eurostat/OCSE) e 'osm' per dati geografici. "
                "Includi sempre il portale/agency di origine quando citi una risorsa."
            ),
            tools=tools,
        )
        reply = await agent.run(
            "Confronta la popolazione di Milano nel 2024 tra ISTAT ed Eurostat, "
            "e disegna i confini comunali su una mappa."
        )
        print(reply.text)

asyncio.run(main())

Streaming dei tool

Per rispondere in streaming (utile in app o CLI interattive) usa agent.run_streaming(...):

async for chunk in agent.run_streaming("Quali dataset ISTAT sul mercato del lavoro?"):
    if chunk.delta_text:
        print(chunk.delta_text, end="", flush=True)
    if chunk.tool_calls:
        for tc in chunk.tool_calls:
            print(f"\n  ↳ tool {tc.name}({tc.arguments})")

Note operative

  • Pre-release pinning: agent-framework è pubblicato come pre-release. pip install --pre è necessario, e in CI conviene fissare la versione esatta.
  • Hostnames in Docker: dentro al docker-compose usa i nomi servizio (http://ckan-mcp:8080/mcp); da host usa localhost.
  • Provider alternativo: se non hai una chiave Anthropic, sostituisci AnthropicChatClient con il client Azure Foundry o Ollama (l'SDK supporta i tre).
  • Rate limit del backend: questo esempio chiama direttamente i server MCP, quindi non passa per i rate limit del backend OpenData AI. Se invece consumi l'endpoint REST/datasets/search il limite è 60 req/min/utente — vedi /docs/rate-limits.