기본적으로 각 호출자에게는 사용자별 호스팅 에이전트 세션 격리에 설명된 대로 전용 호스팅 에이전트 세션이 할당됩니다. Teams 봇, ISV 게이트웨이 또는 고객 지원 플랫폼 등 많은 사용자에게 서비스를 제공하는 애플리케이션은 사용자당 하나의 세션이 필요하지 않습니다. 대신 중간 계층 서비스는 많은 사용자를 제한된 공유 세션 풀에 매핑하고 모든 호출에서 각 사용자를 식별합니다.
이 문서에서는 각 사용자의 데이터를 공유 세션 내에서 격리된 상태로 유지하면서 중간 계층의 사용자 간에 세션을 풀하는 방법을 보여 줍니다.
플랫폼은 사용자가 세션을 공유하는 경우에도 대화 상태를 격리합니다. 한 사용자가 만든 응답 체인은 다른 사용자가 만든 응답 체인을 통해 previous_response_idcontext.get_history() 계속할 수 없으며 현재 요청의 사용자가 볼 수 있는 권한이 부여된 기록만 반환합니다. 사용자가 소유하는 것은 두 가지입니다. 하나는 중간 계층에서의 사용자-세션 매핑이고, 다른 하나는 플랫폼에서 관리되는 해당 대화 상태 외에 컨테이너가 직접 저장하는 모든 데이터(파일, 행 또는 캐시)의 파티셔닝입니다.
실행 가능한 전체 세션 멀티플렉싱 샘플 은 중간 계층 세션 풀 및 컨테이너 처리기 양쪽을 보여 줍니다. 이 문서에서는 진행하면서 해당 파일을 연결합니다.
사전 요구 사항
- 컨테이너 프로토콜 버전 2.0.0을 사용하는 호스트된 에이전트입니다. 업그레이드하려면 호스트된 에이전트 마이그레이션을 참조하세요.
- 중간 계층 서비스의 ID에 할당된
Microsoft.CognitiveServices/accounts/AIServices/agents/endpoints/UserIdentityImpersonation/action권한입니다. 이 권한은 기본 제공 역할에 포함되지 않습니다. 사용자 지정 역할을 통해 부여 - 최종 사용자 ID 위임을 참조하세요. 이것이 없으면x-ms-user-identity헤더는403와 함께 거부됩니다. - 중간 계층에 대한 Azure AI Projects 클라이언트 라이브러리 및 컨테이너에 대한 Azure AI AgentServer SDK(
azure-ai-agentserver-corePython 경우 2.0.0b7 이상 또는Azure.AI.AgentServer.Core.NET 경우 1.0.0-beta.26 이상)입니다. - 테스트 대상으로 사용할 배포된 에이전트. 로컬 실행에는 격리가 적용되지 않습니다.
공유 세션에서 두 사용자 격리
핵심 동작으로 시작합니다. 두 명의 사용자(샘플의 행동 사용자인 Alice와 Bob)는 이를 agent_session_id공유할 수 있으며 플랫폼은 여전히 각 사용자의 대화를 비공개로 유지합니다. 중간 계층은 모든 호출에서 x-ms-user-identity 헤더(위임)를 사용해 위임 대상 사용자를 식별합니다. 사용자 고유의 대화를 계속하려면 해당 사용자의 이전 응답을 다음과 같이 previous_response_id전달합니다.
Azure Developer CLI에서 동일한 ID 헤더를 보내려면 azd ai agent invoke --user-identity를 사용하세요. 예제 및 사용자의 세션 및 대화 상태를 선택하는 방법은 최종 사용자 신원 전달을 참조하세요.
샘플의 최소한의 invoke_previous_response_isolation.py 호출 코드는 SDK의 에이전트 바인딩 Responses 클라이언트를 사용해 바로 그것을 전송합니다:
# 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)
플랫폼은 각 응답 체인을 만든 사용자와 연결합니다. Bob이 같은 세션에 앉아 있는 동안 Alice를 previous_response_id 보내면 통화가 실패합니다. Bob은 Alice의 대화를 계속할 수 없습니다. 이 보장은 컨테이너에 추가 격리 코드 없이 유지됩니다.
세션 풀을 사용하여 많은 사용자로 확장
한 세션에서 두 사용자를 격리하는 것이 구성 요소입니다. 많은 사용자에게 서비스를 제공하려면 사용자마다 세션을 하나씩 여는 대신 제한된 수의 세션에 사용자를 분산해 공유하세요.
각 세션은 턴을 적극적으로 처리하는 동안 지역 동시 세션 제한에 대해 계산되므로 사용자당 하나의 세션은 크기가 조정되지 않습니다. 사용자가 턴 사이에 읽고, 생각하고, 입력하기 때문에 최대 동시 요청은 일반적으로 전체 사용자 수의 작은 부분입니다. 풀 크기를 해당 피크로 조정한 다음, 각 사용자를 세션에 매핑하고 이전 섹션과 똑같이 모든 호출에서 해당 사용자의 ID를 전달합니다.
사용자를 세션에 매핑하는 방법을 결정합니다. 일반적인 전략은 다음과 같습니다.
- 고정, 최소 로드. 반환되는 사용자가 세션을 다시 사용합니다. 새 사용자가 가장 적게 로드된 세션으로 이동합니다. 이 전략은 부하를 균등하게 분산하고 사용자의 회전을 함께 유지합니다. 세션이 사용자별 한도에 도달하면 풀을 확장합니다.
- 해시 기반입니다.
hash(user_id) % pool_size을 사용하여 세션을 할당합니다. 이 전략은 간단하고 무상태이지만, 부하 분산이 고르지 않을 수 있고 풀의 크기를 조정하면 사용자 배치가 다시 뒤섞입니다. - 라운드로빈. 요청을 풀 전체에 균등하게 분산합니다. 이 전략은 간단하지만 사용자의 발화가 서로 다른 세션으로 분산될 수 있습니다.
- 그룹 기반입니다. 관련 사용자가 세션을 공유할 수 있도록 테넌트, 팀 또는 지역별로 라우팅합니다. 이 전략은 그룹의 사용자가 컨텍스트를 공유할 때 유용합니다.
샘플의 invoke_session_pool.py 호출자는 sticky-fill 및 round-robin의 두 가지 전략으로 호출자 소유 할당을 구현합니다. 반환되는 사용자는 항상 세션을 유지합니다. 새 사용자는 선택한 전략에 의해 배치됩니다. 스티키-필 경로는 부하가 가장 낮은 세션을 먼저 채우고, 모든 세션이 용량 한도에 도달했을 때만 새 세션을 엽니다.
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
반환된 세션 ID를 앞서 표시한 것과 동일한 위임 호출에 전달합니다. 그러면 해당 ID는 agent_session_id에서 extra_body가 되고, x-ms-user-identity는 사용자별 식별자로 유지됩니다.
컨테이너에서 요청 처리
프로토콜 2.0.0에서는 플랫폼이 대리 대상 사용자를 확인하고 get_request_context()를 통해 이를 핸들러에 노출합니다. 컨텍스트를 검증한 후(로컬 실행 시처럼 컨텍스트가 없으면 차단되도록), 플랫폼이 context.get_history()를 통해 사용자별 기록을 반환하도록 합니다.
main.py 샘플의 처리기는 자체 대화 상태를 유지하지 않습니다.
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)
플랫폼은 요청당 권한을 부여하므로 context.get_history() 공유 세션의 사용자는 다른 사용자의 대화 기록을 받지 못합니다.
컨테이너가 저장하는 사용자별 데이터 분할
플랫폼은 사용자의 대화 기록을 분리해 관리합니다. 컨테이너가 파일, 데이터베이스 행 또는 캐시와 같은 자체 데이터 도 저장하는 경우 해당 데이터는 자동으로 분할되지 않습니다. 동일한 세션의 두 사용자가 서로의 데이터를 볼 수 없도록 세션 ID와 사용자 ID를 모두 사용하여 키 지정합니다.
partition = (agent_session_id, user_id)
Warning
사용자가 세션을 공유할 때 플랫폼은 컨테이너가 자체 저장하는 데이터를 분할하지 않습니다. 컨테이너가 세션 ID로만 데이터를 키로 지정하면 풀의 모든 사용자가 동일한 데이터를 볼 수 있습니다. 항상 파티션 키에 사용자 ID를 포함합니다.
요청당 플랫폼 컨텍스트에서 사용자 ID를 읽습니다.
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
또한 플랫폼은 사용자 정보를 x-agent-user-id 요청 헤더에 주입합니다. 런타임에서 SDK 컨텍스트를 사용하지 않는 경우 이 헤더를 직접 읽습니다.
플랫폼은 프로토콜 2.0.0에서 get_request_context().user_id를 채웁니다. 둘 이상의 사용자가 세션에 들어갈 수 있는 경우 사용자 소유 데이터에만 세션 ID를 사용하지 마세요.
빌드할 세션별 스토리지의 작업 예제는 메모 작성 에이전트 샘플을 참조하세요.
$HOME 아래의 세션당 하나의 파일을 키로 지정합니다. 공유 세션의 경우 각 사용자가 자신의 파티션을 가져오도록 요청 컨텍스트에서 사용자 ID로 해당 키를 확장합니다.
격리 확인
샘플의 A-A-B 테스트 invoke_previous_response_isolation.py로 보증을 확인합니다. 두 명의 고유 사용자(샘플 기본값은 Alice 및 Bob)를 사용하여 배포된 에이전트에 대해 실행합니다.
- Alice로 공유 세션에서 응답을 생성하고 해당 응답의
id를 캡처합니다. - Alice로 동일한 세션에서 첫 번째 응답의
previous_response_id으로id를 설정한 두 번째 응답을 생성하고, 그id를 캡처합니다. - Bob과 마찬가지로 동일한 세션에서 Alice의 두 번째 응답으로
previous_response_id설정된 요청을 보냅니다. 통화에 실패했습니다 - Bob은 Alice의 통화 연결을 계속할 수 없습니다.
두 개의 다른 Entra 사용자 또는 개체 ID를 사용합니다. 동일한 ID로 확인되는 두 레이블은 유효한 사용자 간 테스트가 아닙니다.
프로토콜 2.0.0 경로에 레거시 격리 헤더를 보내면 해당 모델이 플랫폼 사용자 컨텍스트로 대체되므로 오류가 반환됩니다.
관련 콘텐츠
- 기본 호출자별 격리 모델에 대해 사용자당 호스트된 에이전트 세션을 격리합니다.
- 전체 중간 계층 세션 풀, 컨테이너 처리기 및 격리 테스트에 대한 세션 멀티플렉싱 샘플입니다.
- 세션당 사용자 소유 데이터를 유지하는 컨테이너(Python 및 C#)에 대한 참고 자료 작성 에이전트 샘플입니다.
- 리전별 동시 세션 제한은 Foundry Agent Service의 할당량 및 제한을 참조하세요.
- 호스트된 에이전트를 마이그레이션 하여 컨테이너를 프로토콜 2.0.0으로 이동합니다.
- 컨테이너가 받는 플랫폼 헤더 및 환경 변수에 대한 호스트된 에이전트 런타임 계약입니다.