23 에이전트 배치
에이전트를 배치하면 에이전트가 호스트된 응용 프로그램으로 전환됩니다.
플레이그라운드 또는 다른 AI 컴퓨트에 연결된 동일한 AI 컴퓨트에 에이전트를 배포할 수 있습니다. 에이전트에 대한 최신 변경사항을 연결된 AI 컴퓨트에 배포할 때 배포된 에이전트는 배포 시 에이전트의 스냅샷을 나타냅니다. 배치된 에이전트를 최신 버전으로 업데이트하려면 에이전트를 재배치해야 합니다.
각 에이전트에는 고유한 에이전트 키에 따라 달라지는 안정적인 배포 URL이 있습니다. 에이전트를 여러 번 재배치하면 에이전트가 배치 URL 뒤에 덮어씁니다.
- 에이전트는 지정된 시간에 하나의 AI 컴퓨트 클러스터에만 배치할 수 있습니다.
- 동일한 에이전트를 동일한 AI 컴퓨트 클러스터에 여러 번 배치하면 이전에 배치된 에이전트의 반복이 겹쳐쓰여집니다.
에이전트를 배치한 후에는 채팅 URI를 검색하여 에이전트의 세부정보 탭에서 프로그래밍 방식으로 질의를 실행하고 에이전트에서 응답을 검색할 수 있습니다.
![[세부사항] 탭이 열리고 강조 표시된 상태로 에이전트 페이지가 열립니다. AI 컴퓨트에 배치되고 끝점 URL이 강조 표시됩니다. [세부사항] 탭이 열리고 강조 표시된 상태로 에이전트 페이지가 열립니다. AI 컴퓨트에 배치되고 끝점 URL이 강조 표시됩니다.](img/agentflows-detailsendpointurl.png)
끝점 URL이 안정적이며 각 에이전트에 연결됩니다. URL에는 각 에이전트에 지정된 고유한 agentID가 포함됩니다. 즉, 에이전트의 배치를 해제하고 다시 배치하는 경우 URL은 동일하게 유지됩니다. 단점은 끝점을 호출하는 클라이언트 코드를 수정할 필요가 없다는 것입니다. 단점은 운용 중인 에이전트를 겹쳐쓸 수 있다는 것입니다.
https://gateway.aidp.{oci-region}.oci.oraclecloud.com/agentendpoint/{agentId}/{protocol}oci-region는 AI 데이터 플랫폼 인스턴스 리전에 해당합니다.agentId은 에이전트와 연관된 고유 ID입니다.protocol는 통신 프로토콜입니다.chat는 OpenAI Responses API 형식을 따르고a2a는 에이전트-에이전트 통신 프로토콜을 따릅니다. 각 에이전트 끝점에 대해 두 프로토콜을 모두 사용할 수 있습니다. 자세한 내용은 A2A 에이전트 배치를 참조하십시오.
주:
Details(세부정보) 탭에는 두 개의 AI 컴퓨트가 나열됩니다. Attached to AI Compute(AI 컴퓨트에 연결됨)는 플레이그라운드에서 에이전트를 테스트하는 데 사용됩니다. AI 컴퓨트에 배포됨은 배포된 에이전트를 호스팅합니다.에이전트를 배치하면 끝점 URL 필드가 채워집니다. 운용 애플리케이션에서 이 끝점 URL을 호출할 수 있습니다.
A2A 에이전트 배치
Agent2Agent(A2A) 프로토콜은 서로 다른 프레임워크로 구축된 에이전트, 서로 다른 공급업체에서 호스팅하거나 불투명한 원격 시스템으로 실행되는 에이전트를 포함하여 독립적인 AI 에이전트 간의 통신을 위한 개방형 표준입니다.
이 회사의 목적은 이러한 에이전트가 서로의 기능을 검색하고, 지원되는 입/출력 형식을 협상하고, 작업을 위임 또는 협업하고, 내부 메모리, 도구 또는 구현 세부 정보를 노출하지 않고도 정보를 안전하게 교환할 수 있도록 공유 상호 작용 모델을 제공하는 것입니다. 자세한 내용은 Agent2Agent (A2A) Protocol을 참조하십시오.
A2A는 에이전트 상호 운용성을 해결하기 위한 것입니다. 즉, 모든 에이전트 통합이 사용자 정의되는 대신 클라이언트 또는 다른 에이전트가 공통 개념 및 작업 세트를 사용하여 A2A 호환 원격 에이전트와 상호 작용할 수 있습니다. 사양은 메시지, 작업, 부품, 아티팩트, 스트리밍 업데이트 및 푸시 알림을 중심으로 하며 동기 회신, 장기 실행 비동기 작업, 스트리밍 및 엔터프라이즈 스타일의 인증/보안 패턴을 지원합니다.
Oracle AI Data Platform에서는 배포된 모든 에이전트에 A2A 클라이언트 애플리케이션에서 호출할 수 있는 /A2A 호출 경로가 제공됩니다.
에이전트 카드란 무엇입니까?
에이전트 카드는 A2A 서버에서 게시한 JSON 메타데이터 문서입니다. AIDP에서 A2A 서버는 에이전트 배치를 호스팅하는 AI 컴퓨트입니다.
이 카드는 에이전트의 ID, 서비스 끝점, 지원되는 프로토콜/전송, 기능, 기술, 지원되는 입/출력 모드 및 인증 요구 사항에 대해 설명합니다. 클라이언트는 이 카드를 사용하여 에이전트가 적합한지 여부 및 에이전트의 호출 방법을 파악합니다. 제대로 문서화된 에이전트 카드는 A2A 프로토콜의 요구 사항입니다.
AI 데이터 플랫폼 워크벤치의 에이전트 카드는 초안 상태입니다. 즉, 에이전트가 배치되지 않았거나 게시됨입니다. 즉, 카드가 에이전트와 함께 배치되었습니다.
에이전트 카드 작업
에이전트를 개발하는 동안 에이전트의 작업 메뉴에서 카드를 사용할 수 있습니다.
- 초안 카드는 개발 중인 에이전트의 현재 상태를 반영합니다.
- 게시된 카드는 에이전트가 배포될 때 찍은 카드의 스냅샷에 해당합니다. 게시된 카드는 배치된 에이전트의 상태를 반영합니다.
에이전트 카드 필드
AI Data Platform Workbench는 현재 A2A 프로토콜 에이전트 카드 필드의 하위 세트를 지원합니다(여기서는 A2A 프로토콜 - 에이전트 카드).
| 필드 | 필수사항 | 설명 |
|---|---|---|
name |
예 | 에이전트에 대한 사람이 읽을 수 있는 이름입니다. 예: "레시피 에이전트" |
description |
예 | 에이전트에 대한 사람이 읽을 수 있는 설명으로, 사용자와 다른 에이전트가 해당 목적을 이해할 수 있도록 지원합니다. 예: "사용자에게 조리법 및 요리를 제공하는 데 도움이 되는 에이전트입니다." |
Agent Version |
예 | 에이전트의 버전입니다. 예: "1.0.0" |
Documentation URL |
아니요 | 에이전트에 대한 추가 설명서를 제공하는 URL입니다. |
Provider - Organization |
아니요 | 에이전트의 서비스 제공자입니다. |
Provider - URL |
아니요 | 서비스 제공자의 URL입니다. |
Capabilities |
예 | 에이전트에서 지원하는 A2A 기능 세트입니다.
|
Skills
|
예 | 기술은 에이전트의 능력을 나타냅니다. 그것은 대체로 설명적인 개념이지만 에이전트가 성공할 가능성이 더 큰 일련의 행동을 나타냅니다. 기술은 AgentSkill의 배열을 나타냅니다. |
각 AgentSkill은 에이전트의 기능을 문서화하는 여러 필드로 구성됩니다. 에이전트 카드에서 에이전트 기술 정의는 시간이 가장 많이 걸리는 작업이며 반복 프로세스입니다. 배포 전에 초안 에이전트 카드에서 기술(나머지 에이전트 카드와 함께)을 편집할 수 있습니다.
주:
inputModes, outputModes 및 securityRequirements는 AI 데이터 플랫폼 워크벤치에서 제공되며 수정할 수 없습니다.
| 필드 | 필수사항 | 설명 |
|---|---|---|
Skill ID |
예 | 에이전트 스킬에 대한 고유 식별자입니다. |
Skill Name |
예 | 스킬에 대한 사람이 읽을 수 있는 이름입니다. |
Description |
예 | 스킬에 대한 자세한 설명입니다. |
Tags |
예 | 스킬의 기능을 설명하는 키워드 세트입니다. |
Examples |
아니요 | 이 스킬이 처리할 수 있는 프롬프트 또는 시나리오의 예입니다. |
에이전트 배치 끝점 A2A 경로
/a2a 경로는 /chat 외에도 배포된 에이전트의 URL에 표시됩니다.
예를 들어, 에이전트는 다음 경로를 외부 클라이언트에 노출합니다.
https://gateway.aidp.{oci-region}.oci.oraclecloud.com/agentendpoint/{agentId}/chathttps://gateway.aidp.{oci-region}.oci.oraclecloud.com/agentendpoint/{agentId}/a2a
두 경로(/chat, /a2a) 모두 별도의 클라이언트에서 사용할 수 있습니다.
A2A의 세션 변수
세션 변수의 값은 메시지 metadata 필드에서 A2A 에이전트로 전달할 수 있습니다. 아래 JSON 코드 조각은 세 개의 세션 변수(userName, geoLocation 및 os)가 있는 a2a 에이전트에 발행된 사용자 메시지의 페이로드를 보여줍니다.
{
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"contextId": "session_12345",
"taskId": "task_67890",
"message": {
"role": "user",
"parts": [
{
"text": "What is the current status of my order?",
}
],
"metadata": {
"sessionvariables.userName": "George",
"sessionvariables.geoLocation": “Dallas, TX”,
"sessionvariables.os": "mobile_ios"
}
}
},
"id": "rpc-99821"
}
예: OCI CLI를 사용하여 A2A 에이전트 호출(비스트리밍)
oci raw-request \
--http-method POST \
--auth security_token \
--request-body '{
"id": "<your-request-id>",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"configuration": {
"acceptedOutputModes": [
"text/plain",
"text"
]
},
"message": {
"contextId": "<your-context-id>",
"kind": "message",
"messageId": "<your-message-id>",
"parts": [
{
"kind": "text",
"text": "What is the capital of India?"
}
],
"role": "user"
}
}
}' \
--request-headers '{
"x-session-id": "<your-session-id>",
"dh-user-principal": "<your-user-principal>"
}' \
--target-uri " <your-a2a-agent-endpoint-url>"
예: OCI CLI를 사용하여 A2A 에이전트 호출(스트리밍)
oci raw-request \
--http-method POST \
--auth security_token \
--request-body '{
"id": "<your-request-id>",
"jsonrpc": "2.0",
"method": "message/stream",
"params": {
"configuration": {
"acceptedOutputModes": [
"text/plain",
"text"
]
},
"message": {
"contextId": "<your-context-id>",
"kind": "message",
"messageId": " <your-message-id>",
"parts": [
{
"kind": "text",
"text": "What is the capital of India?"
}
],
"role": "user"
}
}
}' \
--request-headers '{
"x-session-id": "<your-session-id>",
"dh-user-principal": "<user-principal>"
}' \
--target-uri "<your-a2a-agent-endpoint-url>"
예: A2A 클라이언트 SDK
import asyncio
import json
import logging
import typing
from collections.abc import Iterator
import uuid
import httpx
import oci
from a2a.client import A2AClient, ClientFactory
from a2a.types import (
AgentCard,
Message,
Part,
Role,
TextPart,
SendMessageRequest,
MessageSendParams,
MessageSendConfiguration,
Task, SendMessageSuccessResponse, SendStreamingMessageRequest,
)
class OCIAuth(httpx.Auth):
"""httpx auth implementation using OCI signer via requests auth adapter."""
def __init__(self, signer: oci.signer.AbstractBaseSigner):
self._requests_auth = _OCIRequestsAuth(signer)
def auth_flow(self, request: httpx.Request) -> Iterator[httpx.Request]:
req = RequestsRequest(
method=request.method,
url=str(request.url),
headers=dict(request.headers),
data=request.content,
)
prepared: RequestsPreparedRequest = req.prepare()
prepared = self._requests_auth(prepared)
request.headers.update(dict(prepared.headers))
yield request
def getOCIAuth():
conf = oci.config.from_file(profile_name="DEFAULT")
token_file = conf['security_token_file']
token = None
with open(token_file, 'r') as f:
token = f.read()
private_key = oci.signer.load_private_key_from_file(conf['key_file'])
signer = oci.auth.signers.SecurityTokenSigner(token, private_key)
auth = OCIAuth(signer=signer)
return auth
async def _call_agent_with_a2a(agent_url: str, query: str, context_id: str,auth:OCIAuth) -> str:
"""Call an agent using the A2A protocol."""
try:
# Initialize OCI signer
#headers = {"dh-user-principal": "dh-user"}
headers = {"Accept": "*/*",
"dh-user-principal": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
async with httpx.AsyncClient(timeout=60.0, auth=auth,headers=headers) as hc:
agent_card = await _get_agent_card(agent_url,auth)
print(f"Agent card is {agent_card}")
client = A2AClient(httpx_client=hc, agent_card=agent_card)
# Create message
message = Message(
message_id=str(uuid.uuid4()),
context_id=context_id,
role=Role.user,
parts=[Part(root=TextPart(text=query))],
metadata={"sessionvariables.cred.mcp.weatherReportMCP.bearer": "valid-123"}
)
request = SendMessageRequest(
id=str(uuid.uuid4()), # Add the required id field
params=MessageSendParams(
message=message,
configuration=MessageSendConfiguration(acceptedOutputModes=["text/plain", "text"]),
),
)
#json_string = json.dumps(message, indent=4)
print(f"Send request : {request}")
response = await client.send_message(request)
logging.info("Received response from A2A server: %s", response.root.result)
# Extract response
result = response.root.result
# Handle different response types
if isinstance(result, Task):
# Task response
if result.artifacts:
# Extract text from artifacts
texts = []
for artifact in result.artifacts:
for part in artifact.parts:
if hasattr(part, "root") and hasattr(part.root, "text"):
texts.append(part.root.text)
return "\n".join(texts) if texts else "Task completed with no text response"
elif result.status and result.status.message:
logging.info(f"Received Task status {result.status.state} from A2A server and status message is {result.status.message}", result.status.message)
if result.status.state== "failed":
print("Failure observed in Task invocation")
for m_part in result.status.message.parts:
print(f"Error message { m_part.root.text}")
return get_message_text(result.status.message)
else:
return f"Task {result.id} status: {result.status.state if result.status else 'unknown'}"
elif isinstance(result, Message):
return get_message_text(result)
else:
logging.warning(f"Unexpected response type: {type(result)}")
return "Received response but unable to extract text"
except Exception as ex:
logging.error(f"Error calling agent at {agent_url}: {ex}", exc_info=True)
return f"Error communicating with agent: {str(ex)}"
async def _call_agent_with_a2a_with_stream(agent_url: str, query: str, context_id: str, auth: OCIAuth) -> str:
"""Call an agent using the A2A protocol with streaming (SSE) and return the final artifact text."""
try:
async with httpx.AsyncClient(timeout=60.0, auth=auth) as hc:
agent_card = await _get_agent_card(agent_url)
if not agent_card:
return "No Agent Card Found"
print(f"Agent card is {agent_card}")
client = A2AClient(httpx_client=hc, agent_card=agent_card)
message = Message(
message_id=str(uuid.uuid4()),
context_id=context_id,
role=Role.user,
parts=[Part(root=TextPart(text=query))],
)
request = SendStreamingMessageRequest(
id=str(uuid.uuid4()),
params=MessageSendParams(
message=message,
configuration=MessageSendConfiguration(acceptedOutputModes=["text/plain", "text"]),
),
)
print("Invoking Remote Agent request (beautified JSON):")
print(json.dumps(request.model_dump(), indent=2, ensure_ascii=False))
# Expected event types:
# - TaskStatusUpdateEvent (working/in-progress)
# - TaskArtifactUpdateEvent (contains Artifact.parts[].root.text) -> final output
final_artifact_text_parts: list[str] = []
async for event in client.send_message_streaming(request):
# Print each SSE event as-is (SDK object)
print(f"[A2A stream event] {event}")
try:
result = getattr(event.root, "result", None)
if not result:
continue
# TaskArtifactUpdateEvent and TaskStatusUpdateEvent are SDK types; to avoid tight coupling,
# extract by attribute presence.
artifact = getattr(result, "artifact", None)
if artifact and getattr(artifact, "parts", None):
for part in artifact.parts:
root = getattr(part, "root", None)
txt = getattr(root, "text", None)
if txt:
final_artifact_text_parts.append(txt)
except Exception:
# Keep streaming even if an event can't be parsed
continue
return "\n".join([t for t in final_artifact_text_parts if t]).strip() or "Stream completed (no artifact text)."
except Exception as ex:
logging.error(f"Error calling agent at {agent_url}: {ex}", exc_info=True)
return f"Error communicating with agent: {str(ex)}"게시된 에이전트 카드 편집
에이전트를 배치 해제하거나 재배치하지 않고 게시된 에이전트 카드를 수정할 수 있습니다.
주:
게시된 카드에 대한 변경 사항은 즉시 A2A 클라이언트가 액세스할 수 있는 agent-card.json 파일에 반영됩니다.https://gateway.aidp.{oci-region}.oci.oraclecloud.com/agentendpoint/{agentId}/a2a/agent-card.json
![[배치 해제] 단추가 강조 표시된 에이전트 상단의 자른 이미지 [배치 해제] 단추가 강조 표시된 에이전트 상단의 자른 이미지](img/agentflows-undeploy.png)






