Obsługuj wielu użytkowników w jednej hostowanej sesji agenta

Domyślnie każdy wywołujący ma własną sesję agenta hostowanego, jak opisano w Izolowanie sesji agenta hostowanego dla każdego użytkownika. Aplikacje obsługujące wielu użytkowników — takie jak bot usługi Teams, brama ISV czy platforma obsługi klienta — nie wymagają oddzielnej sesji dla każdego użytkownika. Zamiast tego usługa warstwy pośredniej przypisuje wielu użytkowników do ograniczonej puli współdzielonych sesji i identyfikuje każdego użytkownika przy każdym wywołaniu.

W tym artykule pokazano, jak pulować sesje między użytkownikami z warstwy środkowej przy jednoczesnym zachowaniu izolacji danych każdego użytkownika w sesji udostępnionej.

Platforma izoluje stan konwersacji dla Ciebie, nawet jeśli użytkownicy współużytkowali sesję: łańcuch odpowiedzi tworzony przez jednego użytkownika nie może być kontynuowany przez innego użytkownika za pośrednictwem metody previous_response_idi context.get_history() zwraca tylko historię, którą użytkownik bieżącego żądania ma uprawnienia do wyświetlenia. Posiadasz dwie rzeczy: mapowanie użytkownika do sesji w warstwie pośredniej oraz partycjonowanie wszelkich danych, które kontener przechowuje sam (plików, wierszy lub pamięci podręcznej), poza zarządzanym przez platformę stanem konwersacji.

Kompletny, gotowy do uruchomienia przykład multipleksowania sesji przedstawia obie strony — pulę sesji warstwy pośredniej i moduł obsługi kontenera — a w tym artykule w miarę postępów znajdują się odnośniki do jego plików.

Prerequisites

  • Hostowany agent korzystający z protokołu kontenera w wersji 2.0.0. Aby uaktualnić, zobacz Migrowanie hostowanych agentów.
  • Uprawnienie Microsoft.CognitiveServices/accounts/AIServices/agents/endpoints/UserIdentityImpersonation/action, przypisane do tożsamości usługi warstwy pośredniej. To uprawnienie nie jest uwzględnione we wbudowanych rolach; przyznaj je za pomocą roli niestandardowej — zobacz Delegowanie tożsamości użytkownika końcowego. Bez niego nagłówek x-ms-user-identity zostaje odrzucony z komunikatem 403.
  • Biblioteka klienta Azure Projekty sztucznej inteligencji dla warstwy środkowej oraz zestaw SDK Azure AI AgentServer dla kontenera (azure-ai-agentserver-core2.0.0b7+ dla Python lub Azure.AI.AgentServer.Core 1.0.0-beta.26+ dla .NET).
  • Wdrożony agent do przeprowadzania testów. Izolacja nie jest egzekwowana podczas uruchomień lokalnych.

Izolowanie dwóch użytkowników w sesji udostępnionej

Zacznij od podstawowego mechanizmu działania: dwóch użytkowników — nazwijmy ich Alicją i Bobem, użytkownikami, w imieniu których działa się w tym przykładzie — może współdzielić jedno agent_session_id, a platforma nadal zachowuje prywatność rozmowy każdego z nich. Warstwa pośrednia identyfikuje użytkownika, w imieniu którego wykonywane jest działanie, w każdym wywołaniu za pomocą nagłówka x-ms-user-identity (delegacja). Aby kontynuować konwersację użytkownika, przekazuje poprzednią odpowiedź tego użytkownika jako previous_response_id.

Minimalny invoke_previous_response_isolation.py obiekt wywołujący w przykładzie wysyła dokładnie to przy użyciu klienta odpowiedzi powiązanych z agentem zestawu SDK:

# Agent-bound Responses client from the Foundry SDK.
responses_client = project_client.get_openai_client(agent_name=agent_name).responses

# Target the shared session with agent_session_id, and identify the acted-for
# user with x-ms-user-identity (delegation). Pass previous_response_id to
# continue this user's own chain. Don't send x-agent-user-id; Foundry sets the
# container-side request context after it resolves the user.
kwargs = {
    "input": user_message,
    "stream": False,
    "store": True,
    "extra_body": {"agent_session_id": session_id},
    "extra_headers": {"x-ms-user-identity": user_id},
}
if previous_response_id:
    kwargs["previous_response_id"] = previous_response_id

response = responses_client.create(**kwargs)

Platforma łączy każdy łańcuch odpowiedzi z użytkownikiem, który go utworzył. Jeśli Bob wyśle previous_response_id Alicji, będąc w tej samej sesji, wywołanie nie powiedzie się — Bob nie może kontynuować konwersacji Alicji. Ta gwarancja obowiązuje bez żadnego dodatkowego kodu izolującego w kontenerze.

Skalowanie do wielu użytkowników przy użyciu puli sesji

Izolowanie dwóch użytkowników w jednej sesji to blok konstrukcyjny. Aby obsłużyć wielu użytkowników, należy połączyć je w ograniczony zestaw sesji zamiast otwierać jedną sesję na użytkownika.

Każda sesja wlicza się do regionalnych limitów współbieżnych sesji podczas aktywnego przetwarzania tury, więc jedna sesja na użytkownika nie skaluje się. Ponieważ użytkownicy czytają, myślą i piszą między kolejnymi turami, szczytowa liczba jednoczesnych żądań jest zwykle niewielką częścią łącznej liczby użytkowników. Dobierz wielkość puli pod kątem tego szczytowego obciążenia, a następnie przypisz każdego użytkownika do sesji w tej puli i przekazuj tożsamość tego użytkownika przy każdym wywołaniu, dokładnie tak jak w poprzedniej sekcji.

Zdecyduj, jak mapować użytkowników na sesje. Typowe strategie obejmują:

  • Przylepny, najmniej obciążony. Powracający użytkownik ponownie używa swojej sesji; nowi użytkownicy przechodzą do sesji z najmniejszym obciążeniem. Ta strategia równomiernie rozkłada obciążenie i utrzymuje kolejne tury jednego użytkownika razem. Zwiększ pulę, gdy sesje osiągną limit dla poszczególnych użytkowników.
  • Oparte na skrótach. Przypisz sesję za pomocą polecenia hash(user_id) % pool_size. Ta strategia jest prosta i bezstanowa, ale obciążenie może być nierównomierne, a zmiana rozmiaru puli powoduje ponowne przypisanie użytkowników.
  • Round-robin. Równomiernie rozdzielaj żądania w puli. Ta strategia jest prosta, ale wypowiedzi użytkownika mogą trafiać do różnych sesji.
  • Oparte na grupach. Kieruj według dzierżawcy, zespołu lub regionu, aby powiązani ze sobą użytkownicy współdzielili sesje. Ta strategia jest przydatna, gdy użytkownicy w grupie dzielą wspólny kontekst.

Obiekt wywołujący invoke_session_pool.py w przykładzie implementuje przypisanie zarządzane przez obiekt wywołujący za pomocą dwóch strategii, sticky-fill i round-robin. Powracający użytkownik zawsze zachowuje swoją sesję; nowy użytkownik jest umieszczany przez wybraną strategię. Ścieżka sticky-fill wypełnia najmniej załadowaną sesję i otwiera nową tylko wtedy, gdy każda sesja jest w pojemności:

def get_session_for_user(self, user_id: str) -> str:
    if user_id in self.user_to_session:
        return self.user_to_session[user_id]      # returning user is sticky
    session_id = self._next_fill_session()        # new user: place by strategy
    self.user_to_session[user_id] = session_id
    self.session_user_counts[session_id] += 1
    return session_id

def _next_fill_session(self) -> str:
    # Reuse a session with capacity; open a new one only when all are full.
    session_id = next(
        (s for s, count in self.session_user_counts.items()
         if count < self.max_users_per_session),
        None,
    )
    if session_id is None:
        session_id = self._session_name(len(self.session_user_counts))
        self.session_user_counts[session_id] = 0
    return session_id

Przekaż zwrócony identyfikator sesji do tego samego wywołania delegowanego, które pokazano wcześniej: staje się on agent_session_id w extra_body, a x-ms-user-identity pozostaje identyfikatorem poszczególnego użytkownika.

Obsłuż żądanie w swoim kontenerze

W protokole 2.0.0 platforma ustala użytkownika, w imieniu którego wykonywane jest działanie, i udostępnia go procedurze obsługi za pośrednictwem elementu get_request_context(). Zweryfikuj, czy kontekst jest dostępny (jeśli go brakuje, np. podczas uruchomień lokalnych, zakończ działanie odmową), a następnie pozwól platformie zwrócić historię dla każdego użytkownika za pomocą context.get_history(). Program obsługi main.py w przykładzie nie przechowuje własnego stanu konwersacji:

from azure.ai.agentserver.core import get_request_context

@app.response_handler
async def handler(request, context, _cancellation_signal):
    ctx = get_request_context()
    if not (ctx.user_id and ctx.call_id):
        # Hosted protocol 2.0.0 populates this context; off-platform it's absent.
        raise ValueError("A user context is required on protocol 2.0.0.")

    user_input = await context.get_input_text() or "Hello!"
    history = await context.get_history()       # platform-authorized for this user
    input_items = _build_input(user_input, history)

    response = _responses_client.create(model=_model, input=input_items, store=False)
    return TextResponse(context, request, text=response.output_text)

Ponieważ platforma autoryzuje context.get_history() dla każdego żądania, użytkownik w sesji współdzielonej nigdy nie otrzymuje historii konwersacji innego użytkownika.

Partycjonuj dane poszczególnych użytkowników przechowywane przez kontener

Platforma oddziela historię rozmów dla Ciebie. Jeśli kontener przechowuje również własne dane — pliki, wiersze bazy danych lub pamięć podręczną — te dane nie są automatycznie partycjonowane. Użyj jako klucza zarówno identyfikatora sesji, jak i identyfikatora użytkownika, aby dwóch użytkowników w tej samej sesji nie mogło widzieć nawzajem swoich danych:

partition = (agent_session_id, user_id)

Warning

Gdy użytkownicy udostępniają sesję, platforma nie partycjonuje danych, które przechowuje sam kontener. Jeśli kontener identyfikuje te dane wyłącznie na podstawie identyfikatora sesji, każdy użytkownik w puli widzi te same dane. Zawsze dołączaj identyfikator użytkownika do klucza partycji.

Odczytaj identyfikator użytkownika z kontekstu platformy dla każdego żądania:

from azure.ai.agentserver.core import get_request_context

def partition_key() -> tuple[str, str]:
    ctx = get_request_context()
    if not ctx or not ctx.user_id:
        raise PermissionError("A user context is required on protocol 2.0.0.")
    return (ctx.session_id, ctx.user_id)   # key all user-owned data by this

Platforma dodaje również użytkownika jako nagłówek żądania x-agent-user-id. Jeśli środowisko uruchomieniowe nie używa kontekstu zestawu SDK, przeczytaj ten nagłówek bezpośrednio.

Platforma uzupełnia get_request_context().user_id w protokole 2.0.0. Nigdy nie używaj samego identyfikatora sesji dla danych należących do użytkownika, gdy więcej niż jeden użytkownik może wejść do sesji.

Praktyczny przykład użycia pamięci masowej przypisanej do sesji jako punktu wyjścia znajdziesz w przykładzie agenta do tworzenia notatek. Kluczem jest jeden plik na sesję w obszarze $HOME. W przypadku sesji współdzielonej rozszerz ten klucz o identyfikator użytkownika z kontekstu żądania, aby każdy użytkownik miał własną partycję.

Weryfikowanie izolacji

Potwierdź gwarancję za pomocą testu A-A-B próbki, invoke_previous_response_isolation.py. Uruchom to na wdrożonym agencie z dwoma różnymi użytkownikami (domyślnie w przykładzie są to Alice i Bob):

  1. Jako Alicja, utwórz odpowiedź we współdzielonej sesji i zarejestruj jej id.
  2. Jako Alicja, utwórz drugą odpowiedź w ramach tej samej sesji, z previous_response_id ustawionym na id pierwszej odpowiedzi, i przechwyć jej id.
  3. Jako Bob, w tej samej sesji, wyślij żądanie z previous_response_id ustawionym na drugą odpowiedź Alicji. Połączenie kończy się niepowodzeniem — Bob nie może kontynuować łańcucha Alice.

Użyj dwóch różnych użytkowników entra lub identyfikatorów obiektów. Dwie etykiety, które wskazują na tę samą tożsamość, nie stanowią poprawnego testu między różnymi użytkownikami.

Wysłanie starszego nagłówka izolacji w ścieżce protokołu 2.0.0 zwraca błąd, ponieważ ten model jest zastępowany przez kontekst użytkownika platformy.