Gerir sessões de agentes alojadas

Este artigo mostra-lhe como gerir sessões para agentes alojados no Foundry Agent Service. Uma sessão é um sandbox isolado e com estado ligado a uma única carga de trabalho lógica (por exemplo, o chat de um utilizador). A plataforma mantém o sistema de ficheiros da sessão ($HOME e os ficheiros carregados) entre turnos e períodos de inatividade, para que o agente possa retomar de onde ficou. As sessões persistem por até 30 dias, com um tempo limite de inatividade de 15 minutos que desprovisiona os recursos de computação e guarda o estado até que a sessão seja novamente referenciada. Para contexto, veja Agentes hospedados no Foundry Agent Service.

Sessões versus conversas

Sessões e conversas são conceitos distintos no Serviço de Agentes de Fundição:

Aspeto Sessão Conversa
O que representa Computação sandbox e sistema de ficheiros persistente ($HOME, /files) Histórico de mensagens, chamadas de ferramentas e respostas
Identificador agent_session_id previous_response_id ou conversation (apenas protocolo de respostas)
Usado para Carregamentos de ficheiros, estado de funcionamento entre turnos Entrelaçando os turnos de uma conversa
Gerido por A plataforma, através da /sessions API A plataforma (Respostas); o teu código de contentor (Invocações)

A /sessions API neste artigo funciona da mesma forma tanto para os agentes do protocolo Responses como do protocolo Invocations. O que difere é a forma como uma invocação por chamada se liga a uma sessão e se a plataforma armazena o histórico das conversas:

  • Protocolo de respostas. A continuidade da conversa vem de previous_response_id ou de um conversation ID — não do ID da sessão. Reutilizar o mesmo agent_session_id por si só não reproduz mensagens anteriores para o modelo. Tens duas formas de encadear os turnos de respostas.

    • previous_response_id. Encadeie cada nova resposta ao ID da resposta anterior. Apátrida no cliente. Cada chamada sem uma indicação explícita agent_session_id inicia num novo sandbox, por isso inclui agent_session_id também quando necessitas de reutilizar ficheiros carregados ou estado $HOME.
    • conversation. Crie um objecto de conversação uma vez via POST .../endpoint/protocols/openai/conversations, e depois passe-o id em cada responses.create chamada. A plataforma armazena o histórico de mensagens sob esse ID de conversa, e um stable agent_session_id é automaticamente associado à conversa. As chamadas subsequentes reutilizam o mesmo sandbox sem teres de rastrear o ID da sessão.
  • Protocolo de invocações. A plataforma não armazena o histórico das conversas; o seu contentor gere qualquer estado que o seu agente necessite ao longo das interações. As sessões continuam úteis para a persistência de ficheiros $HOMEcarregados e como um handle estável que o seu código pode usar para aceder ao seu próprio estado por sessão.

Dica

Enviar agent_session_id de volta para chamadas de seguimento liga-os ao mesmo ambiente de teste. Os endpoints de invocação não inferem a sessão a partir de previous_response_id isoladamente. Só precisas dessa ligação quando os turnos posteriores tiverem de ver os ficheiros carregados através do endpoint da sessão /files ou o estado para o qual o agente escreveu $HOME. Para invocações puramente sem estado, podes deixar que cada chamada tenha uma sessão nova.

Como cada protocolo vincula uma invocação a uma sessão

Os dois endpoints de invocação aceitam o ID da sessão em locais diferentes. Usa o mecanismo mostrado para o protocolo que estás a chamar.

Protocolo Ponto final Onde colocar agent_session_id
Respostas POST .../endpoint/protocols/openai/responses Campo no corpo do pedido agent_session_id (ou usar o campo conversation, que associa automaticamente uma sessão)
Invocações POST .../endpoint/protocols/invocations Parâmetro de string de consulta ?agent_session_id=<id>

Para Invocações, a plataforma lê apenas o parâmetro de consulta. Campos nomeados agent_session_id ou session_id no corpo do pedido e cabeçalhos como x-agent-session-id são passados para o seu contentor sem serem tocados, mas não influenciam para qual sandbox a plataforma se direciona.

Pré-requisitos

  • CLI do Azure versão 2.80 ou posterior, autenticado com az login.
  • Python SDK: azure-ai-projects>=2.1.0 e azure-identity.
  • Azure Developer CLI versão 1.23.0 ou posterior.

  • A extensão dos agentes da Foundry:

    azd ext install azure.ai.agents
    

Configurar variáveis

Os exemplos de APIs REST neste artigo usam az rest para chamar diretamente os endpoints do Foundry Agent Service. Defina as seguintes variáveis antes de executar os comandos:

ACCOUNT_NAME="<your-foundry-account-name>"
PROJECT_NAME="<your-project-name>"
BASE_URL="https://${ACCOUNT_NAME}.services.ai.azure.com/api/projects/${PROJECT_NAME}"
API_VERSION="v1"
RESOURCE="https://ai.azure.com"

Importante

O parâmetro --resource é necessário para todas as chamadas az rest aos endpoints do plano de dados do Foundry Agent Service. Sem ele, o az rest não consegue derivar a audiência Microsoft Entra correta a partir do URL e a autenticação falha.

Nota

As operações de sessão são uma funcionalidade de pré-visualização. Inclua o Foundry-Features: HostedAgents=V1Preview cabeçalho em cada pedido REST.

Configurar o cliente

Todos os exemplos de Python neste artigo utilizam a seguinte configuração de cliente:

from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient

project = AIProjectClient(
    endpoint="<your-project-endpoint>",
    credential=DefaultAzureCredential(),
    allow_preview=True,
)

Nota

As operações de sessão são expostas sob o project.beta.agents subcliente. Chamadas para project.beta.agents funcionam sem allow_preview=True, mas project.get_openai_client(agent_name=...)—usado neste artigo para invocar agentes do protocolo Respostas—requer allow_preview=True e gera ValueError sem ela.

Invoca um agente e deixa a plataforma criar a sessão

Para a maioria dos agentes, não é necessário criar uma sessão antecipadamente. Quando invoca o agente sem um agent_session_id, o serviço cria um e devolve-o na resposta. Captura esse ID e passa-o para chamadas posteriores quando quiseres que invocações subsequentes partilhem o mesmo estado sandbox — por exemplo, ficheiros que carregas através do /files endpoint.

Nota

A continuidade da sessão não é o mesmo que a continuidade da conversa. Para as Respostas, preserve o histórico de mensagens entre turnos ao passar previous_response_id ou um ID conversation. Para Invocações, a plataforma não armazena o histórico — o código do seu contentor é responsável por rastrear o estado de cada sessão. Em ambos os casos, agent_session_id apenas liga as chamadas ao mesmo sandbox.

Protocolo de Respostas

O endpoint Respostas devolve ou uma única carga útil JSON ou um fluxo de Eventos Enviados pelo Servidor (SSE), dependendo do campo do corpo do pedido. O padrão é false. Definir "stream": true para receber eventos incrementais.

AGENT_NAME="my-agent"

az rest --method POST \
    --url "${BASE_URL}/agents/${AGENT_NAME}/endpoint/protocols/openai/responses?api-version=${API_VERSION}" \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview" \
    --body '{
        "input": "Find me hotels in Seattle under $200 per night",
        "stream": false
    }'

A carga útil de resposta inclui a plataforma agent_session_id criada. Para continuar a usar essa sessão numa chamada posterior ao fazer threading com previous_response_id, inclua agent_session_id no corpo do pedido:

az rest --method POST \
    --url "${BASE_URL}/agents/${AGENT_NAME}/endpoint/protocols/openai/responses?api-version=${API_VERSION}" \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview" \
    --body '{
        "input": "Recommend one of those hotels",
        "stream": false,
        "agent_session_id": "<session-id-from-first-response>",
        "previous_response_id": "<id-from-first-response>"
    }'

Se fizer thread turns com um conversation ID em vez disso, a plataforma encaminha automaticamente todas as chamadas dessa conversa para a mesma agent_session_id. Não precisas de passar agent_session_id por ti próprio.

Quando chama get_openai_client com um agent_name, o cliente OpenAI devolvido é encaminhado para o endpoint do agente. A primeira chamada cria a sessão; a resposta traz o novo agent_session_id em model_extra.

openai_client = project.get_openai_client(agent_name="my-agent")

response = openai_client.responses.create(
    input="Find me hotels in Seattle under $200 per night",
)
session_id = response.model_extra.get("agent_session_id")
print(f"Session: {session_id}")
print(f"Response: {response.output_text}")

# Reuse the session and thread the conversation on a later turn.
follow_up = openai_client.responses.create(
    input="Recommend one of those hotels",
    previous_response_id=response.id,
    extra_body={"agent_session_id": session_id},
)
print(follow_up.output_text)

Se fizer interações com um conversation ID em vez de previous_response_id, a plataforma encaminha automaticamente todas as chamadas dessa conversa para o mesmo agent_session_id—pode omitir o extra_body={"agent_session_id": ...}.

conversation = openai_client.conversations.create()

first = openai_client.responses.create(
    input="Find me hotels in Seattle under $200 per night",
    extra_body={"conversation": conversation.id},
)
follow_up = openai_client.responses.create(
    input="Recommend one of those hotels",
    extra_body={"conversation": conversation.id},
)
azd ai agent invoke --input "Find me hotels in Seattle under $200 per night"

Protocolo de invocações

Os agentes de invocação aceitam corpos arbitrários de pedidos JSON que correspondem ao esquema definido pelo seu contentor. A plataforma dirige a chamada para uma sessão e envia o corpo para o seu container; a forma de resposta é qualquer coisa que o seu container emita. Agentes e contentores de amostra construídos com o azure-ai-agentserver-invocations SDK devolvem um fluxo SSE por defeito, com um evento terminal done que transporta o session_id (o mesmo valor que agent_session_id) e um invocation_id. O seu próprio contentor pode devolver JSON, NDJSON, SSE ou qualquer outro tipo de conteúdo.

A primeira chamada sem parâmetro agent_session_id de consulta cria uma nova sessão. Para reutilizar o sandbox numa chamada posterior, passe o ID da sessão como agent_session_id parâmetro de consulta.

AGENT_NAME="my-agent"

# First call — platform creates a new session.
az rest --method POST \
    --url "${BASE_URL}/agents/${AGENT_NAME}/endpoint/protocols/invocations?api-version=${API_VERSION}" \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview" \
    --body '{"input": "Hello"}'

Para um contentor que emite SSE, como as amostras do Serviço de Agente, o evento final done contém session_id e um invocation_id. Para reutilizar esse sandbox numa chamada posterior, passe o ID da sessão como agent_session_id parâmetro de consulta:

SESSION_ID="<session_id-from-first-response>"

az rest --method POST \
    --url "${BASE_URL}/agents/${AGENT_NAME}/endpoint/protocols/invocations?api-version=${API_VERSION}&agent_session_id=${SESSION_ID}" \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview" \
    --body '{"input": "Continue our previous discussion"}'

Importante

O endpoint Invocations lê o ID da sessão da cadeia de consulta. Campos nomeados agent_session_id ou session_id no corpo do pedido e cabeçalhos como x-agent-session-id são encaminhados para o seu contentor sem alterações, mas não alteram o sandbox para o qual a plataforma é direcionada.

O SDK Python não inclui um cliente Invocations tipado. Chame o endpoint com requests (ou qualquer biblioteca HTTP) e autentique com um token portador de azure-identity:

import json
import requests
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
token = credential.get_token("https://ai.azure.com/.default").token
headers = {
    "Authorization": f"Bearer {token}",
    "Foundry-Features": "HostedAgents=V1Preview",
    "Content-Type": "application/json",
}

base = "<your-project-endpoint>/agents/my-agent/endpoint/protocols/invocations?api-version=v1"

# First call — platform creates a new session.
response = requests.post(base, headers=headers, data=json.dumps({"input": "Hello"}))
for line in response.iter_lines(decode_unicode=True):
    if line.startswith("data:"):
        event = json.loads(line[len("data:"):].strip())
        if event.get("type") == "done":
            session_id = event["session_id"]

# Reuse the session on a later call.
requests.post(
    f"{base}&agent_session_id={session_id}",
    headers=headers,
    data=json.dumps({"input": "Continue our previous discussion"}),
)
azd ai agent invoke --input '{"input": "Hello"}'

Chaves de isolamento

A chave de isolamento é um valor de âmbito associado a cada sessão. Cada sessão pertence exatamente a uma chave de isolamento, e a plataforma encaminha os pedidos de sessão para que o chamador só veja e opere nas sessões marcadas com a chave fornecida pelo pedido. A mesma chave aplica-se consistentemente em todos os endpoints relacionados com sessões, independentemente do protocolo que o agente utilize:

  • POST/GET/DELETE .../endpoint/sessions[/{id}]
  • POST .../endpoint/protocols/openai/responses (Agentes de resposta)
  • POST .../endpoint/protocols/invocations (Agentes de invocações)
  • PUT/GET/DELETE .../endpoint/sessions/{id}/files[/content]

A chave de isolamento é um valor de partição, não um mecanismo de autenticação ou autorização. O token Microsoft Entra na solicitação autentica o chamador e autoriza a chamada em relação às atribuições de funções do projeto. A chave de isolamento limita apenas as sessões em que o chamador autenticado atua. Aplique os controlos de acesso da sua aplicação numa camada acima do endpoint do agente quando precisar de decidir que utilizadores podem atuar sobre quais chaves.

A forma como a chave é definida depende do esquema de autorização do endpoint do agente, que configura quando configura o agente (ver Configurar um agente):

  • Entra (predefinição). A plataforma deriva a chave de isolamento a partir do token Microsoft Entra do chamador. O x-ms-user-isolation-key cabeçalho é aceite mas ignorado. Cada chamador autenticado recebe automaticamente o seu próprio âmbito.
  • Header. A plataforma lê a chave de isolamento a partir do x-ms-user-isolation-key cabeçalho da solicitação. Transmita uma string estável para cada dono da sessão (por exemplo, um identificador de utilizador final ou de inquilino) em cada pedido de sessão, incluindo invocações e operações com ficheiros. Pedidos sem o cabeçalho falham. A plataforma não valida o valor, por isso o seu cliente é responsável por escolher a chave certa para cada chamada.

Crie uma sessão de forma explícita (avançada)

Crie uma sessão antecipadamente apenas quando for necessário:

  • Carregue ficheiros para o sandbox (/files) antes do primeiro turno do agente.
  • Pré-aloca uma sessão que possas consultar a partir do código do cliente antes da primeira invocação.
  • Fixe a sessão a uma versão específica do agente com version_indicator. Cada sessão está vinculada a uma única versão no momento da criação. Por padrão, a plataforma resolve a versão usando as regras de encaminhamento de tráfego do endpoint do agente (version_selector)—por exemplo, @latest. Passa version_indicator para sobrepor isso e vincula a sessão a uma versão concreta (como "2") para que os turnos seguintes continuem a usar essa versão mesmo que publiques uma mais recente. O valor deve ser um identificador de versão concreto devolvido pela API de versões do agente; Pseudónimos como @latest não são aceites aqui.
AGENT_NAME="my-agent"

az rest --method POST \
    --url "${BASE_URL}/agents/${AGENT_NAME}/endpoint/sessions?api-version=${API_VERSION}" \
    --resource "${RESOURCE}" \
    --headers "x-ms-user-isolation-key=user-123" "Foundry-Features=HostedAgents=V1Preview" \
    --body '{
        "version_indicator": {
            "type": "version_ref",
            "agent_version": "2"
        }
    }'

Omita o corpo (ou enviar {}) para permitir que a plataforma escolha a versão usando as regras de encaminhamento de tráfego do endpoint do agente.

session = project.beta.agents.create_session(
    agent_name="my-agent",
    body={},
    isolation_key="user-123",
)
print(f"Session created (ID: {session.agent_session_id}, status: {session.status})")

O SDK requer a isolation_key palavra-chave em create_session e delete_session. O servidor só a aplica quando o endpoint do agente está configurado para ler chaves de cabeçalhos — ver Chaves de isolamento.

Para atribuir a sessão a uma versão específica do agente, inclua version_indicator no corpo:

session = project.beta.agents.create_session(
    agent_name="my-agent",
    body={
        "version_indicator": {"type": "version_ref", "agent_version": "2"},
    },
    isolation_key="user-123",
)

As sessões são criadas automaticamente quando se invoca um agente através de azd. A criação manual de sessões não está atualmente disponível como comando autónomo.

Listar sessões

az rest --method GET \
    --url "${BASE_URL}/agents/my-agent/endpoint/sessions?api-version=${API_VERSION}" \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview"
sessions = project.beta.agents.list_sessions(agent_name="my-agent")
for item in sessions:
    print(f"Session: {item.agent_session_id} (status: {item.status})")

A lista de sessões não está atualmente disponível como comando autónomo. Use a API REST ou o SDK.

Obtenha detalhes das sessões

SESSION_ID="<session-id>"

az rest --method GET \
    --url "${BASE_URL}/agents/my-agent/endpoint/sessions/${SESSION_ID}?api-version=${API_VERSION}" \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview"
session = project.beta.agents.get_session(
    agent_name="my-agent",
    session_id="<session-id>",
)
print(f"Session ID: {session.agent_session_id}, Status: {session.status}")

A gestão de sessões não está atualmente disponível como comando autónomo. Use a API ou SDK REST.

Eliminar uma sessão

Eliminar uma sessão termina o sandbox e liberta os seus recursos. Quando o endpoint do agente usa Header isolamento, a chave de isolamento deve corresponder ao valor usado quando a sessão foi criada. Quando o endpoint usa Entra isolamento, a plataforma limita a eliminação ao âmbito da identidade de chamada.

SESSION_ID="<session-id>"
ISOLATION_KEY="user-123"

az rest --method DELETE \
    --url "${BASE_URL}/agents/my-agent/endpoint/sessions/${SESSION_ID}?api-version=${API_VERSION}" \
    --resource "${RESOURCE}" \
    --headers "x-ms-user-isolation-key=${ISOLATION_KEY}" "Foundry-Features=HostedAgents=V1Preview"
project.beta.agents.delete_session(
    agent_name="my-agent",
    session_id="<session-id>",
    isolation_key="user-123",
)

A gestão de sessões não está atualmente disponível como comando autónomo. Utilize a API REST ou SDK.

Operações de ficheiros de sessão

Carregar e descarregar ficheiros nas sandboxes de sessão de agentes. Cada ficheiro está associado a uma sessão específica. O tamanho máximo do ficheiro para upload é de 50 MB.

Nota

O SDK Python usa session_id como palavra-chave para upload_session_file, e agent_session_id para get_session_files, download_session_file e delete_session_file. Use o nome da palavra-chave mostrado em cada exemplo.

Carregar um ficheiro

SESSION_ID="<session-id>"

az rest --method PUT \
    --url "${BASE_URL}/agents/my-agent/endpoint/sessions/${SESSION_ID}/files/content?api-version=${API_VERSION}&path=data.csv" \
    --resource "${RESOURCE}" \
    --body @data.csv \
    --headers "Content-Type=application/octet-stream" "Foundry-Features=HostedAgents=V1Preview"
project.beta.agents.upload_session_file(
    agent_name="my-agent",
    session_id="<session-id>",
    content_or_file_path="./data.csv",
    path="data.csv",
)

O content_or_file_path parâmetro aceita uma cadeia de caminho do ficheiro. O SDK lê e carrega automaticamente o conteúdo do ficheiro.

azd ai agent files upload --file ./data.csv --target-path data.csv

Listar ficheiros numa sessão

SESSION_ID="<session-id>"

az rest --method GET \
    --url "${BASE_URL}/agents/my-agent/endpoint/sessions/${SESSION_ID}/files?api-version=${API_VERSION}&path=." \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview"
files = project.beta.agents.get_session_files(
    agent_name="my-agent",
    agent_session_id="<session-id>",
    path=".",
)
for entry in files.entries:
    print(f"  {entry['name']} (size: {entry['size']}, directory: {entry['is_directory']})")
azd ai agent files list .

Descarregue um ficheiro

SESSION_ID="<session-id>"

az rest --method GET \
    --url "${BASE_URL}/agents/my-agent/endpoint/sessions/${SESSION_ID}/files/content?api-version=${API_VERSION}&path=data.csv" \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview" \
    --output-file output.csv
content_bytes = b"".join(
    project.beta.agents.download_session_file(
        agent_name="my-agent",
        agent_session_id="<session-id>",
        path="data.csv",
    )
)
with open("./output.csv", "wb") as f:
    f.write(content_bytes)
azd ai agent files download --file data.csv --target-path ./output.csv

Eliminar um ficheiro

SESSION_ID="<session-id>"

az rest --method DELETE \
    --url "${BASE_URL}/agents/my-agent/endpoint/sessions/${SESSION_ID}/files?api-version=${API_VERSION}&path=data.csv" \
    --resource "${RESOURCE}" \
    --headers "Foundry-Features=HostedAgents=V1Preview"
project.beta.agents.delete_session_file(
    agent_name="my-agent",
    agent_session_id="<session-id>",
    path="data.csv",
)
azd ai agent files remove --file data.csv

Próximos passos