Microsoft Foundry hosted agent를 uv 단일 파일 스크립트로
만들고 로컬 PC에서 바로 배포하는 최소 예제입니다. 의존성은
PEP 723 인라인 메타데이터로 선언합니다.
pyproject.toml도, 관리해야 할 requirements.txt도, Dockerfile도, CI 파이프라인도
필요 없습니다.
에이전트는 프레임워크별로 서로 바꿔 쓸 수 있는 두 가지 버전으로 제공됩니다. 둘 다
동일한 OpenAI 호환 /responses 프로토콜을 사용하며, 배포 스크립트 하나로 어느 쪽이든
배포할 수 있습니다.
| 파일 | 역할 |
|---|---|
agent_msaf.py |
Microsoft Agent Framework 기반 에이전트. |
agent_lg.py |
동일한 에이전트의 LangGraph 버전. |
deploy.py |
로컬 배포 스크립트. publish → 버전 생성 → active 대기 → 엔드포인트 라우팅 → 스모크 테스트 순으로 진행합니다. 두 에이전트 중 아무거나 인수로 받습니다. |
.env.example |
에이전트를 로컬에서 띄울 때 쓰는 템플릿. .env로 복사해서 사용합니다. |
🇺🇸 The English version of this document is README.md.
🔷 C# 버전 — 이 샘플이 이식해 온 .NET 원본은 별도 gist에 있습니다: Hosted Agent — .NET File-Based App 샘플.
두 샘플은 단계별로 서로 대응하며, 의미 있는 차이는 .NET 샘플과 다른 점에 정리했습니다.
- uv 0.9 이상 — docs.astral.sh/uv에서 설치하십시오. 시스템에 Python이 설치되어 있지 않아도 됩니다. uv가 필요한 인터프리터를 직접 받아옵니다.
- Azure CLI 2.80 이상 — 버전이 낮으면
az upgrade를 실행하십시오. - 모델 배포가 하나 이상 있는 Microsoft Foundry 프로젝트
- 본인 계정에 대한 프로젝트 스코프의 Foundry Project Manager 역할
- 역할을 할당할 수 있는 권한 — 아래 3. 에이전트 자신의 ID에 역할 부여를 보십시오.
로그인한 뒤, Foundry 데이터 플레인 토큰이 실제로 발급되는지 확인하십시오.
az login
az account get-access-token --scope https://ai.azure.com/.default
az account show는 로컬 프로필 파일만 읽기 때문에 갱신 토큰이 만료된 뒤에도 성공합니다. 위의get-access-token명령이 진짜 검증입니다.
deploy.py는 셸에서 FOUNDRY_로 시작하는 환경변수만 읽습니다.
PowerShell
$env:FOUNDRY_PROJECT_ENDPOINT = 'https://<account>.services.ai.azure.com/api/projects/<project>'
$env:FOUNDRY_MODEL_NAME = '<모델 배포 이름>'cmd
set FOUNDRY_PROJECT_ENDPOINT=https://<account>.services.ai.azure.com/api/projects/<project>
set FOUNDRY_MODEL_NAME=<모델 배포 이름>bash
export FOUNDRY_PROJECT_ENDPOINT='https://<account>.services.ai.azure.com/api/projects/<project>'
export FOUNDRY_MODEL_NAME='<모델 배포 이름>'FOUNDRY_MODEL_NAME은 Foundry 프로젝트에 실제로 배포된 배포 이름입니다. 모델 계열
이름이 아니라는 점에 주의하십시오.
프레임워크를 골라 해당 스크립트를 넘기십시오.
uv run deploy.py agent_msaf.py # Microsoft Agent Framework
uv run deploy.py agent_lg.py # LangGraph두 번째 인수로 에이전트 이름을 직접 지정할 수도 있습니다.
uv run deploy.py agent_msaf.py my-agent생략하면 파일명에서 파생합니다. agent_msaf.py는 agent-msaf, agent_lg.py는 agent-lg가
되므로, 둘 다 배포하면 한 에이전트의 두 버전이 아니라 서로 독립된 에이전트 두 개가
프로젝트에 생깁니다. 이름은 영숫자로 시작하고 끝나야 하며, 중간에 하이픈을 쓸 수
있고, 최대 63자입니다. 이 이름이 에이전트 호출 URL에 사용됩니다.
밑줄(
_)은 허용되지 않습니다. 이름의 출처인 Python 파일명에서는 밑줄이 지극히 정상이기 때문에 걸려 넘어지기 쉽습니다.agent_msaf.py는agent-msaf로 파생되지만,agent_msaf를 직접 인수로 넘기면 거부됩니다.deploy.py는 직접 넘긴 이름을 이 규칙에 미리 검사하고 올바른 형태를 제안합니다. 그러지 않으면 publish, vendoring, 압축, 수 MB 업로드가 모두 끝난 뒤에야 서버가 의미를 알기 어려운(invalid_parameters) ValidationError로 답합니다.
agent_msaf.py에는 이 작업이 전혀 필요 없습니다. 두 에이전트는 호출하는 모델 경로가
다르고, 그 두 경로는 Azure RBAC에서 서로 다른 데이터 작업이기 때문입니다.
| 경로 | 역할 할당 필요 | |
|---|---|---|
agent_msaf.py |
POST /openai/v1/responses |
아니오 |
agent_lg.py |
POST /openai/v1/chat/completions |
예 |
호스팅 에이전트의 관리 ID는 Responses 경로 — 자기 자신의 프로토콜 표면 — 는 사용할 수
있지만, chat/completions는 범용 추론이라 명시적인 권한 부여가 필요합니다. ID 모델도,
엔드포인트도, 자격 증명도 같고 경로만 다릅니다.
실제 프로젝트에서 확인할 수 있습니다. Foundry 계정 스코프의 역할 할당 목록을 보면
agent_msaf의 ID에는 아무 역할도 없는데 정상 응답한다는 것을 알 수 있습니다.
ChatOpenAI에use_responses_api=True를 주면agent_lg.py도 Responses 경로로 바뀌고 실제로 역할 요구가 사라집니다. 다만azure-ai-agentserver-langgraph1.0.0b17이 그 경로에서 LangChain이 내보내는 청크를 변환하지 못합니다. 스트리밍에서 델타가 하나도 나오지 않고response.output_text.done이 빈 문자열이 되어, 논스트리밍은 되는데 플레이그라운드에는 빈 답변이 표시됩니다. 어댑터가 이를 처리하기 전까지는 역할을 부여하는 쪽이 더 쌉니다.
배포가 Active에 도달하고 트래픽 라우팅까지 끝난 뒤에도 첫 호출에서 실패할 수 있습니다.
컨테이너는 사용자 계정이 아니라 에이전트와 함께 생성된 자신의 관리 ID로 실행되며, 이
ID에는 역할 할당이 하나도 없는 상태로 시작합니다. 모델 호출은 데이터 작업이므로, 역할이
없으면 첫 호출이 이렇게 돌아옵니다.
401 PermissionDenied — The principal `<guid>` lacks the required data action
Microsoft.CognitiveServices/accounts/OpenAI/deployments/chat/completions/action
deploy.py는 버전이 Active가 되는 즉시 그 principal을 [ident ] 줄에 출력합니다. Foundry
리소스 스코프에서 Foundry User 역할을 부여하십시오.
az role assignment create \
--assignee-object-id <ident 줄에 출력된 principal id> \
--assignee-principal-type ServicePrincipal \
--role 53ca6127-db72-4b80-b1b0-d745d6d5456d \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<account>그런 다음 deploy.py를 다시 실행하십시오. 버전은 콘텐츠 주소 기반이라 번들이 그대로면
다시 업로드되지 않으므로, 사실상 스모크 테스트만 다시 도는 셈입니다.
이 명령에서 두 가지를 짚어 둡니다. 역할을 이름이 아니라 id로 넘기는 이유는 최근 이름이 바뀌었기 때문입니다. Foundry User의 옛 이름이 Azure AI User이고, 이름 변경이 진행되는 동안 두 이름이 함께 통용됩니다. 그리고 이름이 Cognitive Services로 시작하는 역할은 쓰지 마십시오. 그것들은 AI Services 리소스를 직접 겨냥한 역할이며 Foundry 프로젝트에는 적용되지 않습니다.
배포할 때마다가 아니라 에이전트마다 한 번입니다. ID는 에이전트에 속하고 새 버전이
올라가도 그대로이므로, 재배포해도 역할 할당은 유지됩니다. 다만 에이전트 이름마다 각자의
ID를 갖습니다. agent_lg.py 옆에 agent_msaf.py를 배포하면 별도의 principal이 생겨 따로
부여해야 하고, 에이전트를 삭제한 뒤 다시 만들면 새 ID가 생겨 다시 부여해야 합니다. 특정
에이전트가 어떤 ID로 실행 중인지는 이렇게 확인합니다.
next(iter(sorted(client.agents.list_versions(name), key=lambda v: int(v.version)))).instance_identity.principal_idagents/read 권한이 없으면 대화 기록을 가져오지 못한다는 경고가 먼저 나타납니다. 치명적이지는
않아서 에이전트는 기록 없이 응답하지만, 원인과 해결책은 위와 동일합니다.
스크립트는 마지막에 스모크 테스트(ping)를 실행하고 응답을 출력합니다.
Foundry 포털에서 아래 프롬프트로도 확인해 보십시오.
What is Microsoft Foundry?
Explain how hosted agents work in Foundry.
Give me three ideas for a weekend project.
Summarize the benefits of using the Agent Framework.
두 스크립트는 의도적으로 동일한 에이전트입니다. 지시문도, 모델도, 엔드포인트도 같습니다.
그래서 남는 차이가 곧 프레임워크의 차이입니다. Foundry는 둘을 구분하지 않습니다. 양쪽 모두
Responses 프로토콜을 말하는 hosted agent이고, deploy.py도 별도 플래그 없이 둘 다
처리합니다.
agent_msaf.py |
agent_lg.py |
|
|---|---|---|
| 프레임워크 | Microsoft Agent Framework | LangGraph |
| 에이전트 객체 | Agent(FoundryChatClient(...), instructions=...) |
create_agent(model, tools=[], system_prompt=...) → CompiledStateGraph |
| 호스트 어댑터 | ResponsesHostServer(agent).run() |
from_langgraph(graph).run(port=...) |
| 호스팅 패키지 | agent-framework-foundry-hosting |
azure-ai-agentserver-langgraph |
| 모델 클라이언트 | FoundryChatClient(프로젝트와 직접 통신) |
프로젝트의 AsyncOpenAI 클라이언트를 감싼 ChatOpenAI |
| 번들 크기 | 압축 약 19 MiB | 압축 약 45 MiB |
| 수신 포트 | PORT를 직접 읽음 |
DEFAULT_AD_PORT를 읽으므로, 스크립트가 PORT를 명시적으로 전달 |
어느 쪽을 확장하든 미리 알아둘 만한 점이 두 가지 있습니다.
두 호스팅 스택은 메이저 버전이 다릅니다. agent-framework-foundry-hosting은
azure-ai-agentserver-core 2.x 위에 올라가고, azure-ai-agentserver-langgraph는 1.x를
고정합니다. 두 스택은 한 환경을 공유할 수 없습니다. 이 샘플에 단일 파일 스크립트가
잘 맞는 이유가 바로 이것입니다. 각 스크립트가 자기 해석 결과를 따로 갖고, 서로를 제약하지
않습니다.
LangGraph는 프로젝트의 OpenAI 호환 경로로 모델에 접근합니다. 그 URL을 직접 조립하는
대신, agent_lg.py는 AIProjectClient.get_openai_client()에 AsyncOpenAI 클라이언트를
요청합니다. 이 클라이언트는 이미 {프로젝트 엔드포인트}/openai/v1을 가리키고 있고, 스스로
갱신되는 Entra ID 베어러 토큰 공급자에 연결되어 있습니다. 그 클라이언트를 ChatOpenAI에
넘깁니다. deploy.py가 스모크 테스트에 쓰는 인증 경로와 동일합니다. 스크립트의
api_key="unused"는 ChatOpenAI가 키 없이는 생성되지 않기 때문에 두는 자리표시자일
뿐입니다.
셋 다 자명하지 않고, 하나같이 배포가 성공한 뒤 첫 호출에서, 엉뚱한 곳을 가리키는 메시지와 함께 실패합니다.
from_langgraph()에는 시그니처와 달리 credentials가 사실상 필수입니다. 어댑터는 매
요청마다 프로젝트의 Foundry 도구 레지스트리를 해석하며, 도구를 하나도 선언하지 않은
그래프도 예외가 아닙니다. 자격 증명이 없으면 ThrowingFoundryToolRuntime이 만들어지고,
첫 호출은 HTTP 200에
{"code": "server_error", "message": "FoundryToolRuntime is not configured..."}를 담아
돌려줍니다.
어댑터는 FOUNDRY_PROJECT_ENDPOINT가 아니라 AZURE_AI_PROJECT_ENDPOINT를 읽습니다.
후자는 v1 agent-server 시절의 이름입니다. Foundry가 주입하는 것은 전자이고,
from_langgraph()는 엔드포인트 인수를 받지 않으므로 환경변수가 유일한 접점입니다.
agent_lg.py는 os.environ.setdefault로 둘을 이어 줍니다. 이걸 빠뜨리면 자격 증명을
넘겼더라도 같은 FoundryToolRuntime is not configured 오류가 납니다.
ChatOpenAI에는 root_async_client와 async_client를 함께 넘겨야 합니다. 클라이언트
생성 여부를 판단하는 조건이 async_client만 보고 root_async_client는 보지 않기 때문에,
루트만 넘기면 조건이 충족되지 않아 LangChain이 api.openai.com을 향하는 기본 클라이언트를
만들고 두 필드를 조용히 덮어씁니다. 증상은 설정한 적도 없는 호스트를 지목하는
401 Incorrect API key provided: unused입니다.
도구를 추가하려면 agent_lg.py의 tools=[] 목록에 넣거나(LangGraph가 도구 호출 루프를
라우팅합니다), agent_msaf.py의 Agent(...)에 tools=를 전달하십시오. 어느 쪽이든 호스팅
계약은 달라지지 않습니다.
두 에이전트 모두 .env 파일(python-dotenv 사용) 또는 프로세스 환경변수에서 설정을
읽습니다. deploy.py가 쓰는 변수들과는 서로 다른 집합이라는 점에 주의하십시오.
cp .env.example .env
# .env 를 편집한 뒤, 둘 중 하나를 실행:
uv run agent_msaf.py
uv run agent_lg.py첫 실행에서 해당 스크립트의 인라인 의존성을 해석해 캐시 환경에 설치하며, 이후 실행은 곧바로 시작됩니다. 두 스크립트는 서로 다른 캐시 환경을 사용하므로 간섭하지 않습니다.
호스트는 8088 포트에 바인딩되며(PORT로 변경 가능), GET /readiness에 준비 상태
프로브를 노출합니다.
1턴 — 에이전트 호출:
curl -sS -X POST http://localhost:8088/responses \
-H "Content-Type: application/json" \
-d '{"input": "What is Microsoft Foundry?", "stream": false}' | jq .2턴 — 이전 응답의 id로 이어서 질문:
curl -sS -X POST http://localhost:8088/responses \
-H "Content-Type: application/json" \
-d '{"input": "Can you summarize that?", "previous_response_id": "<id>", "stream": false}' | jq .멀티턴 대화는 자동으로 동작합니다. 프레임워크가 매 요청마다 이전 턴들로부터 대화 기록을 다시 구성합니다. 로컬에서는 이 기록이 프로세스 안에 있어 재시작하면 사라지고, Foundry에 호스팅되면 서버 측 영구 저장소를 사용합니다.
| 변수 | 필수 | 기본값 | 설명 |
|---|---|---|---|
FOUNDRY_PROJECT_ENDPOINT |
예 | — | 대상 프로젝트의 데이터 플레인 엔드포인트. 포털의 프로젝트 개요 페이지에서 복사합니다. |
FOUNDRY_MODEL_NAME |
예 | — | 모델 배포 이름. 컨테이너에 AZURE_AI_MODEL_DEPLOYMENT_NAME으로 주입됩니다. |
FOUNDRY_AUTH |
아니오 | cli |
cli는 AzureCliCredential만 사용합니다. default는 DefaultAzureCredential 체인을 사용합니다(CI / 관리 ID용). |
FOUNDRY_TENANT_ID |
아니오 | — | 여러 테넌트에 로그인되어 있을 때 대상 테넌트를 고정합니다. |
기본값이 cli인 이유. DefaultAzureCredential은 EnvironmentCredential을 먼저
시도합니다. 머신에 AZURE_CLIENT_ID / AZURE_TENANT_ID / AZURE_CLIENT_SECRET이 남아
있으면 그 서비스 주체로 인증을 시도하고, 실패 시 다음 자격 증명으로 넘어가지 않고
ClientAuthenticationError를 던져 체인이 그 자리에서 끊깁니다(AADSTS700016 등).
az login을 해두어도 그 지점까지 도달하지 못합니다. 기본값을 cli로 두면 로컬 배포
결과가 결정론적으로 유지됩니다.
| 변수 | 필수 | 설명 |
|---|---|---|
FOUNDRY_PROJECT_ENDPOINT |
예 | 호스팅 환경에서는 플랫폼이 자동 주입합니다. 로컬 실행 시에만 직접 설정하십시오. |
AZURE_AI_MODEL_DEPLOYMENT_NAME |
예 | 호스팅 시 deploy.py가 FOUNDRY_MODEL_NAME 값으로 설정합니다. |
APPLICATIONINSIGHTS_CONNECTION_STRING |
아니오 | 호스팅 환경에서는 자동 주입됩니다. 로컬에서 Application Insights로 추적을 보내려면 설정하십시오. 없으면 에이전트가 경고를 출력합니다. |
PORT |
아니오 | 수신 포트. 기본값 8088. |
FOUNDRY_PROJECT_ENDPOINT와APPLICATIONINSIGHTS_CONNECTION_STRING은 플랫폼이 주입하는 예약 변수이므로, hosted agent의 환경변수로 선언하지 마십시오.
- export —
uv export --script <agent>.py가 PEP 723 인라인 메타데이터 블록을 완전히 고정된 requirements 목록으로 해석합니다. 선언된 의존성과 그것들이 끌고 오는 모든 패키지가 정확한 버전으로 나열됩니다. - vendor —
uv pip install --target으로 그 패키지들을 내 PC가 아니라 컨테이너의 플랫폼에 맞춰 설치합니다(--python-platform x86_64-manylinux_2_28 --python-version 3.13). 이 교차 해석이 없으면 Windows나 macOS에서 만든 번들에 리눅스 컨테이너가 로드할 수 없는 바이너리 확장이 조용히 들어갑니다.--only-binary :all:은 "해당 플랫폼용 휠 없음"을 잘못된 인터프리터 기준의 소스 빌드 대신 이 자리에서의 오류로 만듭니다. - entry point 확인 — 스크립트는 vendoring된 패키지 옆, 번들 루트에 복사됩니다.
그래야
python <name>.py가 그 디렉터리를sys.path맨 앞에 두어 임포트가 해결됩니다. 대신 스크립트 자신의 이름이 모듈 네임스페이스에 들어가므로,azure.py나json.py같은 이름은 실제 모듈을 가려버립니다. 표준 라이브러리와 번들 패키지 양쪽에 대해 충돌을 검사하는 이유는, 그러지 않으면 첫 호출 때424 session_not_ready로만 드러나기 때문입니다. - pack — 전체를 zip으로 묶되 내용이 아카이브 루트에 오도록 합니다.
bin/은 제외합니다. 내 PC용으로 생성된 콘솔 스크립트 런처(Windows에서는.exe심)일 뿐이고, 컨테이너는 위의 entry point만 실행합니다. - 인증 —
https://ai.azure.com/.default에 대한 프리플라이트get_token으로, API 파이프라인 깊숙한 곳이 아니라 여기서 원인이 드러나는 메시지와 함께 빠르게 실패합니다. - 버전 생성 —
create_version_from_code가 zip을 업로드합니다(SDK가 SHA-256 계산을 대신합니다). 콘텐츠 주소 기반 버전 관리이므로 코드와 정의가 모두 동일하면 새 버전을 만들지 않고 기존 버전을 그대로 반환합니다. - active 대기 — 5초 간격으로 폴링하며 10분 데드라인을 둡니다.
bundled의존성 해석은 서버 빌드를 건너뛰므로 빠르게 끝납니다. - 엔드포인트 라우팅 — 새 버전이 트래픽 100%를 받도록 에이전트를 패치합니다. 트래픽 분할은 아직 지원되지 않습니다.
- 스모크 테스트 — 컨테이너는 첫 호출 시점에 세션 단위로 기동하므로, entry point 오류나 readiness 실패는 여기서 처음 드러납니다. 생략할 수 없는 단계입니다.
- 정리 — 임시 디렉터리는 성공 시 삭제하고 실패 시 남겨둡니다. 스테이징된 번들을
열어보며 원인을 추적할 수 있습니다. 경로는
[publish]로그 줄에 출력됩니다.
더 오래된 manylinux2014 태그(glibc 2.17)가 호환성 면에서 더 안전해 보이지만 그렇지
않습니다. numpy 2.5를 비롯해 점점 더 많은 패키지가 이 태그용 휠을 더 이상 배포하지 않기
때문에, numpy에 도달하는 의존성 그래프(LangGraph가 그렇습니다)는 아예 해석에 실패합니다.
manylinux_2_28(glibc 2.28: RHEL 8, Debian 10, Ubuntu 20.04 이상)이 현재 휠들이 실제로
겨냥하는 기준이며, Foundry 런타임 이미지도 이를 만족합니다. TARGET_PLATFORM,
TARGET_PYTHON, PYTHON_RUNTIME은 deploy.py 상단에 함께 모여 있고, 서로 일관되게
유지되어야 합니다.
전체 구조는 동일합니다. 갈라지는 지점은 "publish"의 실제 동작 방식입니다.
| .NET file-based app | uv 단일 파일 스크립트 | |
|---|---|---|
| 인라인 의존성 | #:package Foo@1.2.3 |
PEP 723 # /// script 블록 |
| 실행 | dotnet run agent.cs |
uv run agent_msaf.py |
| Linux용 publish | dotnet publish -r linux-x64 |
uv export + uv pip install --target --python-platform |
| 압축 | SDK가 폴더 경로를 받아 처리 | deploy.py가 직접 수행. SDK는 스트림을 받음 |
| entry point | ["dotnet", "<assembly>.dll"], *.runtimeconfig.json에서 역산 |
["python", "<name>.py"], 모듈 가림 여부를 검사 |
| 런타임 ID | dotnet_10 |
python_3_13 |
| 함정 | publish 기본값이 NativeAOT이므로 꺼야 함 | 크로스 플랫폼 휠, 그리고 스크립트 이름의 모듈 가림 |
양쪽 모두 dependency_resolution: bundled를 사용합니다. 업로드한 트리를 서버 빌드 없이
그대로 실행하며, 인라인 의존성 메타데이터와 프라이빗 패키지 인덱스가 문제없이 동작하는
이유가 바로 이것입니다.
Foundry 호스팅 패키지들은 프리릴리스로 배포되고, 그것들이 끌어오는 Azure SDK 의존성도
프리릴리스 범위로 고정되어 있습니다. uv는 기본적으로 프리릴리스를 거부하며, 더 좁은
explicit 모드는 스크립트에 직접 쓴 마커만 인정하고 전이 의존성은 인정하지 않습니다.
그래서 두 에이전트 스크립트 모두 해석 전체를 opt-in 합니다.
# [tool.uv]
# prerelease = "allow"deploy.py는 이 설정이 필요 없습니다. 안정 버전 azure-ai-projects로만 동작합니다.
deploy.py는 임시 디렉터리 안에서 스테이징과 압축을 수행하므로, 정상 경로에서는 작업
트리를 전혀 건드리지 않습니다.
하지만 같은 과정을 직접 실행하면 이야기가 다릅니다. uv pip install --target ./vendor는
수십 MB에 달하는 패키지를 트리에 떨어뜨리고, uv export -o requirements.txt는 소스가 아니라
생성 산출물인 파일을 만듭니다. 함께 제공되는 .gitignore는 이 경우들과 함께,
흔히 쓰는 디렉터리 이름(vendor/, bundle/, publish-*/, site-packages/), 패키징
산출물(*.zip, *.whl, dist/), Python 런타임 산출물(__pycache__/, *.so, *.pyd,
.venv/), 도구 캐시(.ruff_cache/, .mypy_cache/, .pytest_cache/), Azure 도구
상태(.azure/, .azd/), 비밀 파일(.env, *.pem)까지 덮습니다. .env.example은 예외로
추적됩니다.
uv run 자체는 안전합니다. 환경을 uv 전용 캐시 디렉터리에 두기 때문에 작업 트리에는
아무것도 쓰지 않습니다.
vendoring된 번들을 직접 확인하고 싶다면, 이미 무시되는 경로로 출력하십시오.
uv export --script agent_lg.py --format requirements-txt --no-hashes --no-header --no-annotate > requirements.txt
uv pip install -r requirements.txt --target ./vendor \
--python-version 3.13 --python-platform x86_64-manylinux_2_28 \
--only-binary :all: --link-mode copy| 증상 | 원인과 해결 |
|---|---|
Failed to acquire a token |
갱신 토큰 만료. az login --tenant <tenant-id>를 실행하십시오. az account show는 이 상황에서도 성공한다는 점을 기억하십시오. |
AADSTS700016 |
환경에 AZURE_CLIENT_ID / AZURE_CLIENT_SECRET이 남아 있습니다. FOUNDRY_AUTH=cli로 실행하거나 해당 변수를 제거하십시오. |
az를 찾을 수 없음 |
cmd /c az version으로 확인하십시오. Windows에서 Azure CLI는 .cmd 심(shim)이라 로그인 셸이 아닌 환경의 PATH에서 빠져 있을 수 있습니다. |
No solution found when resolving script dependencies |
의존성 그래프 어딘가에 프리릴리스가 필요합니다. 에이전트 스크립트의 [tool.uv] prerelease = "allow" 블록이 # /// 울타리 안에 있고 모든 줄이 주석 처리되어 있는지 확인하십시오. |
vendoring 중 <pkg> has no usable wheels |
해당 패키지에 TARGET_PLATFORM용 휠이 없습니다. deploy.py의 manylinux 기준을 올리거나, 휠이 있는 버전으로 고정하거나, 의존성을 제거하십시오. |
컨테이너 기동 시 ModuleNotFoundError |
거의 항상 플랫폼 불일치입니다. 번들이 잘못된 OS나 Python 버전으로 vendoring된 것입니다. deploy.py의 TARGET_PLATFORM / TARGET_PYTHON은 PYTHON_RUNTIME과 일치해야 합니다. |
httpx를 언급하는 ImportError |
openai 3이 httpx2로 옮겨갔지만 azure-ai-projects는 여전히 httpx를 임포트합니다. openai>=2.45,<3 고정을 유지하십시오. |
shadows the standard library module |
스크립트 파일명이 실제 모듈과 충돌합니다. 이름을 바꾸십시오. |
버전 생성 시 (invalid_parameters) ValidationError |
에이전트 이름이 유효하지 않습니다. 대개 두 번째 인수로 넘긴 이름에 밑줄이 들어간 경우입니다. 하이픈을 쓰십시오(agent_msaf가 아니라 agent-msaf). |
is not a valid Foundry agent name |
같은 문제를, 업로드가 시작되기 전에 로컬에서 잡아낸 것입니다. 메시지에 올바른 형태가 제안됩니다. |
424 session_not_ready |
entry point 불일치 또는 컨테이너 기동 실패. 포털의 버전 상세를 확인하십시오. |
The agent deployed but failed on its first invocation |
버전은 Active이고 라우팅도 끝났으며, 컨테이너 자체가 실패한 것입니다. 메시지에 서버가 준 code / message가 그대로 담깁니다. uv run <agent>.py로 로컬에서 동일한 경로를 재현할 수 있습니다. |
401 PermissionDenied ... lacks the required data action |
에이전트의 관리 ID에 역할이 없습니다. 3. 에이전트 자신의 ID에 역할 부여를 보십시오. 프레임워크가 아니라 ID 수준의 문제이므로 두 에이전트 모두에 동일하게 해당합니다. |
does not have permissions for ... AIServices/agents/read (경고) |
같은 역할 누락의 약한 증상입니다. 대화 기록을 가져오지 못해 멀티턴 문맥이 사라지지만 단일 턴 응답은 정상입니다. |
FoundryToolRuntime is not configured |
LangGraph 전용. from_langgraph()에 credentials를 넘기지 않았거나 AZURE_AI_PROJECT_ENDPOINT가 비어 있습니다. agent_lg.py가 반드시 맞춰야 하는 세 가지를 보십시오. |
401 Incorrect API key provided: unused |
LangGraph 전용. ChatOpenAI가 주입한 클라이언트를 api.openai.com을 향하는 기본 클라이언트로 바꿔치기한 것입니다. root_async_client와 함께 async_client도 넘기십시오. |
로컬 실행 시 CERTIFICATE_VERIFY_FAILED |
사내 TLS 검사(Zscaler 등). httpx는 certifi를 쓰며 OS 신뢰 저장소를 보지 않습니다. certifi의 cacert.pem과 사내 루트 CA를 이어붙인 번들을 만들어 SSL_CERT_FILE로 지정하십시오. 호스팅 컨테이너에는 해당하지 않습니다. |
호스팅에서는 되는데 로컬에서 AADSTS700016 |
셸에 AZURE_CLIENT_ID / AZURE_TENANT_ID / AZURE_CLIENT_SECRET이 남아 있습니다. DefaultAzureCredential이 az login보다 그 서비스 주체를 먼저 시도합니다. 로컬 실행 시에는 제거하십시오. |
Cannot create item resource helper / OutputTextEventGenerator did not process message (경고) |
LangGraph 전용이며 표시상의 문제입니다. LangChain이 내용이 빈 AIMessageChunk(맨 앞 청크와 finish_reason만 담은 마지막 청크)를 내보내는데, 어댑터의 이벤트 생성기에 내용이 없는 청크를 처리하는 분기가 없어 로그만 남기고 건너뜁니다. 텍스트를 담은 청크는 모두 정상 변환되며, 스트림은 완전한 output_text.done과 response.completed로 끝납니다. |
Conversation with ID conv_... not found (경고, 404) |
대화의 첫 턴에서는 정상입니다. 가져올 기록이 아직 없을 뿐이고 어댑터는 No historical items found로 계속 진행합니다. 같은 경고의 403 형태와는 다르며, 403은 역할 할당 누락을 뜻합니다. |
missing a PEP 723 '# /// script' metadata block 경고 |
에이전트에 인라인 의존성 메타데이터가 없어, 함께 번들될 패키지가 하나도 없습니다. |
에이전트가 PORT를 무시함 |
LangGraph 호스트만 그렇습니다(DEFAULT_AD_PORT를 읽습니다). agent_lg.py는 이미 PORT를 명시적으로 전달하므로, 마지막 줄을 수정하더라도 그 인수는 유지하십시오. |
버전이 Active로 전이하지 않음 |
10분 데드라인 후 TimeoutError. 포털에서 버전의 error 객체를 확인하십시오. |
Provisioning failed |
포털의 버전 상세 페이지를 여십시오. 거기 있는 error.code / error.message가 실제 진단입니다. |
- 세
.py파일 모두#!/usr/bin/env -S uv run --script셔뱅을 갖고 있어, Unix 계열에서는chmod +x agent_msaf.py && ./agent_msaf.py로 실행할 수 있습니다. - 에이전트는
cpu: "0.5",memory: "1Gi"로 배포됩니다. 더 필요하면deploy.py의HostedAgentDefinition을 조정하십시오. - 번들은 압축 기준 약 19 MB(Agent Framework)에서 45 MB(LangGraph) 정도입니다. 250 MB 업로드 제한에는 한참 못 미치지만, 의존성을 늘릴 때 염두에 두십시오.
- 두 에이전트 모두
azure.identity.aio의 비동기DefaultAzureCredential을 사용합니다. 비동기 Azure SDK로 프로젝트와 통신하므로 토큰 갱신이 이벤트 루프를 막으면 안 되기 때문입니다. 평범한 스크립트인deploy.py는 동기 버전을 사용합니다. - 여기 고정된 패키지 버전들은 프리뷰입니다. API 변경을 감안하십시오.