Шматагентныя банкаўскія працэсы на Kubernetes: LangGraph, рэестр і AgentGateway
Будуем спецыялізаваных агентаў на FastAPI і аркестратар LangGraph з LLM-планавальнікам, разгортваем іх у кластары з рэестрам і дадаём маршрутызацыю і ліміты праз AgentGateway.
MCP адаптаваны для сітуацыі, калі агент взаімадзейвае з інструментам: фіксаваная функцыя выкаанае адзін конкрэтны завод і вяртае свой рэзультат, без якога-лібо автонаміі чыраку інструмента. Комунікацыя межа агентамі — гэта ўжо іншая справа. Агент, які выступае на протыяжэнні лініі, можа сам ствараць выводы, возможна, за дапамою сябеўскага LLM, сябеўскіх інструментоў і сябеўскіх рашэнняў, а першы агент дае яму падзаданне і чакае обоснаванага адказу, а не проста рэзультата пошуку.
Працэс адміністрування рахунку паказвае сутнасць гэтай проблемы. Ён включае калькольныя незалежныя аспекты: парабяранне ідэнтычнасці, історыю кліента, пераканальнэ следчасць і перагляд крымінальных дакументаў, фінансавую спроможнасць і дакументы з гасударствавых баз. Кожны з гэтых аспектаў можа належаць да разных каманд, будавацца окрэма і в падсумку рассматрывацца адначасова. Самэ гэтая проблема ў тым, для чаго існуе архітэктура агентаў. Практычны прыклад, паказаны ніжэй, дазваляе перайсці ад аднаго агента да каманды спецялістаў: організатора на базе LangGraph з планавальнікам на базе LLM, рэзыментацыі на Kubernetes, рэжыстрару і, нарэшце, шлюза пад часавам.
Пяць спецялістаў, адна схема
Каманда па адкрыванні рахунку складаецца з пяць спецыялізаваных агентаў. Кожны з іх — самастойная служба FastAPI на сваёй порцыі, пры чым агент па парабяранню ідэнтычнасці задае шаблон: простая служба FastAPI з аднім канцэнтрам.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI(title="Identity Verification Agent")
class VerifyRequest(BaseModel):
customer_id: str
document_type: str
document_number: str
class VerifyResponse(BaseModel):
customer_id: str
status: str
confidence: float
notes: str
@app.get("/health")
async def health():
return {"agent": "identity-verification", "status": "ok"}
@app.post("/invoke", response_model=VerifyResponse)
async def invoke(req: VerifyRequest):
return VerifyResponse(
customer_id=req.customer_id,
status="VERIFIED",
confidence=0.97,
notes=f"{req.document_type} {req.document_number} matched on file.",
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=9001)
Кожны агент у сістэме выконваець той самы прыем: модэль запіту Pydantic, модэль адпаведзя Pydantic, адна точка входу /invoke і адна точка входу /health. Швыракі пераконтроль:
curl -s http://localhost:9001/health
curl -s -X POST http://localhost:9001/invoke \
-H "Content-Type: application/json" \
-d '{"customer_id":"CUST-5567","document_type":"passport","document_number":"X1234567"}'
Іншыя службы ствараюцца абсолютна такім жа спосабам, і ў канцы застаецца восьмі службы.
Першы оркестрайзер: паралельныя вызовы
Першая версія оркестрайзера вызывала всі восьмі агентаў одночасна за дапамогою asyncio.gather і сінтазавала рашэнне на адной паўнай базе ўсіх рэзультатаў:
async def call_agent(client, name, payload):
try:
resp = await client.post(f"{AGENTS[name]}/invoke", json=payload, timeout=10.0)
resp.raise_for_status()
return resp.json()
except httpx.HTTPError as e:
return {"status": "ERROR", "error": str(e)}
@app.post("/open-account")
async def open_account(app_data: AccountApplication):
async with httpx.AsyncClient() as client:
identity, history, background, financial, public_records = await asyncio.gather(
call_agent(client, "identity", {...}),
call_agent(client, "customer_history", {...}),
call_agent(client, "background_check", {...}),
call_agent(client, "financial_capability", {...}),
call_agent(client, "public_records", {...}),
............
............
)
# decision synthesized from all five results
Гэтыя абмежнення прымусілі развіваць дызайн. Кожны агент быў вызываны ў кожны раз, нават калі пачатковая перапаказка ужо зазнала нявынік. Дзеяныя перапаказкі коштувалі рэальных грошаў, адтолькі што запыты ў бэкграундных і публічных базах дадзеных зазвычай выкананыя за дапамогою платных сторонніх прадаўцоў, таму павтарныя вызывы былі проста марнатратнасцю. Код таксама не раздзеляў внутрашняе падстава прыметкі ад сообшчэння, якое паказвалася кліенту. Работа з адказамі на аберанні сводзілася да аднае загальнай структуры try/except, якая абгружала ўсе вызывы, замест таго, каб існавала можлівасць перагляду кожнага крока.
Рашэнням ёсць справжній оркестрактар LangGraph, дзе кожны крок можа пераглядаць тое, што было ранейш, і правільна направляць працэс. Памагальны код, які вызывае агента, ніколі не выкалічвае кэш. Кожная аберанне стае status='ERROR', таму граф правільна направляе аберанні, замест таго, каб сістэма зламалася:
async def call_agent(agent_name: str, payload: dict) -> dict:
"""Never raises -- every failure mode becomes status='ERROR' so the
graph can route uniformly instead of crashing."""
url = f"{AGENTS[agent_name]}/invoke"
try:
async with httpx.AsyncClient(timeout=TIMEOUT_SECONDS) as client:
resp = await client.post(url, json=payload)
resp.raise_for_status()
return resp.json()
except httpx.TimeoutException:
return {"status": "ERROR", "error": f"{agent_name} agent timed out after {TIMEOUT_SECONDS}s"}
except httpx.ConnectError:
return {"status": "ERROR", "error": f"{agent_name} agent is unreachable"}
except httpx.HTTPStatusError as e:
return {"status": "ERROR", "error": f"{agent_name} agent returned {e.response.status_code}"}
except Exception as e:
return {"status": "ERROR", "error": f"{agent_name} agent call failed: {e}"}
def route_after_identity(state) -> str:
result = state.get("identity_result") or {}
if result.get("status") == "ERROR":
state.setdefault("reasons", []).append(f"Identity check failed: {result.get('error')}")
return "decline"
if result.get("status") != "VERIFIED":
state.setdefault("reasons", []).append("Identity could not be verified.")
return "decline"
return "continue"
# ... one routing function per check, same shape ...
builder = StateGraph(ApplicationState)
builder.add_node("identity_check", identity_check)
builder.add_node("customer_history_check", customer_history_check)
builder.add_node("background_check", background_check)
builder.add_node("financial_capability_check", financial_capability_check)
builder.add_node("public_records_check", public_records_check)
builder.add_node("decline", decline_node)
builder.add_node("approve", approve_node)
builder.add_edge(START, "identity_check")
builder.add_conditional_edges("identity_check", route_after_identity,
{"continue": "customer_history_check", "decline": "decline"})
# ... same pattern chained through all five checks ...
builder.add_edge("decline", END)
builder.add_edge("approve", END)
graph = builder.compile()
Гэта простая рашытка LangGraph, у якой кожны крок апрацоўваецца з адпаведным карыстаннем для раскідвання памілак. Яна яшчэ не мае той сложнасці, якая трэба для рэальных працэсаў.
Адны планавальнік LLM для многах продуктам
Ачыцце рахунак — гэта толькі адні з банкавых продуктам. Банк таксама обслугоўвае крэдытныя, дэбетныя і подарунковыя карткі, і кожная з іх выкалічвае разныя пераказы. Жорсткое заданне умов if/else па тыпу продукту не дазволяе масштабавання, таму планавальнік LLM (мадэль GPT праз Azure OpenAI) прыймае запіт на англійскай мове разам з каталогам таго, што прысвячае кожны агент, пасля чаго выбірае стосунавяныя агенты і ўпорадак іх дзеяння. Гэта ёсць рассуджэнне, а не логіка разгалужэння.
Да первачнага набору дадзеныя трохі агентам, прысвечаных конкрэтным продуктам, што стварае тры працэсы: ачыцце банкавы рахунак, прыўязка дэбетнай карткі і пакупка подарунковай карткі.
# A simple starter for all agents
uv run agents/background_check_agent.py &
uv run agents/card_linking_agent.py &
uv run agents/identity_agent.py &
uv run agents/customer_history_agent.py &
uv run agents/public_records_agent.py &
uv run agents/financial_capability_agent.py &
uv run agents/gift_card_compliance_agent.py &
uv run orchestrator_langgraph_llm.py &
chmod a+x ./start_agent.sh
# Run it
./start_agents.sh
(account-opening-agents) krishnansriram@Krishnans-MacBook-Pro account-opening-agents % INFO: Started server process [22183]
INFO: Started server process [22184]
INFO: Started server process [22185]
INFO: Started server process [22182]
INFO: Started server process [22186]
INFO: Started server process [22187]
INFO: Started server process [22188]
INFO: Waiting for application startup.
INFO: Waiting for application startup.
INFO: Waiting for application startup.
INFO: Waiting for application startup.
INFO: Waiting for application startup.
INFO: Waiting for application startup.
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Application startup complete.
INFO: Application startup complete.
INFO: Application startup complete.
INFO: Application startup complete.
INFO: Application startup complete.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:9007 (Press CTRL+C to quit)
INFO: Uvicorn running on http://0.0.0.0:9001 (Press CTRL+C to quit)
INFO: Uvicorn running on http://0.0.0.0:9005 (Press CTRL+C to quit)
INFO: Uvicorn running on http://0.0.0.0:9008 (Press CTRL+C to quit)
INFO: Uvicorn running on http://0.0.0.0:9003 (Press CTRL+C to quit)
INFO: Uvicorn running on http://0.0.0.0:9002 (Press CTRL+C to quit)
INFO: Uvicorn running on http://0.0.0.0:9004 (Press CTRL+C to quit)
INFO: Started server process [22190]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:9000 (Press CTRL+C to quit)
Экзэкютар стаў аднам узлом, які пераглядае будзь-які план, створаны LLM, і больш не следуе фіксаванай лямбэце графа. Ёго пераканалі безпасабліва, прычымляючыся да рэальнага вызову LLM, калі тое трэбуе актуальных крантактаў Azure, падаючы яму ручна створаныя планы:
- План з карточкай подарунковых грошаў з аднім крокам,
gift_card_compliance, быў адзначаны як прыемны ў межах праграмы протыяварэння валютных пранаёмаў, але адхілены па яе пераганянні, прычымляючыся да іншых крокаў. - Трыкрокавы план з крэдытной карткай, які включаў крокі
identity,financial_capabilityіcredit_limit, выканаў усе тры крокі ў правым парадку з правымі рэзультатамі.
Маленькі локальны скрыпт запускае кожнага агента адразу, які зберагаецца як start_agent.sh:
curl -s -X POST http://localhost:9000/process-enquiry \
-H "Content-Type: application/json" \
-d '{
"enquiry_text": "I would like to open a new checking account",
"customer_id": "CUST-9003",
"full_name": "Jordan Alex Smith",
"date_of_birth": "1990-04-12",
"document_type": "passport",
"document_number": "X1234567",
"declared_annual_income": 95000,
"address": "123 Main St, Columbus, OH"
}' | python3 -m json.tool
Handling connection for 9000
{
"customer_id": "CUST-9003",
"product_type": "account_opening",
"planned_steps": [
"identity",
"financial_capability",
"background_check",
"public_records"
],
"planner_reasoning": "This is a new deposit account opening request. Identity should be verified first for the new customer, followed by financial capability checks needed for account opening. Background screening and public records checks are also required for opening a new deposit account.",
"decision": "APPROVED",
"customer_message": "Your account opening request has been approved.",
"reasons": [
"All required checks passed."
],
"step_results": {
"identity": {
"customer_id": "CUST-9003",
"status": "VERIFIED",
"confidence": 0.97,
"notes": "passport X1234567 matched on file."
},
"financial_capability": {
"customer_id": "CUST-9003",
"status": "PASS",
"income_verified": true,
"estimated_credit_score": 742,
"affordability_status": "ADEQUATE"
},
"background_check": {
"customer_id": "CUST-9003",
"status": "PASS",
"criminal_record_found": false,
"sanctions_hit": false,
"watchlist_hit": false,
"notes": "No adverse findings for Jordan Alex Smith."
},
"public_records": {
"customer_id": "CUST-9003",
"status": "PASS",
"address_verified": true,
"bankruptcy_history": false,
"litigation_history": false
}
}
}
Калі агенты працуюць, адправіце тэставы запіт:
curl -s -X POST http://localhost:9000/process-enquiry \
-H "Content-Type: application/json" \
-d '{
"enquiry_text": "I would like to link a debit card to my checking account",
"customer_id": "CUST-9002",
"full_name": "Priya Nair",
"date_of_birth": "1994-03-08",
"document_type": "passport",
"document_number": "X5566778",
"declared_annual_income": 65000,
"address": "12 Maple Rd, Dublin, OH",
"linked_account_number": "ACC-99887766"
}' | python3 -m json.tool
Handling connection for 9000
{
"customer_id": "CUST-9002",
"product_type": "debit_card",
"planned_steps": [
"customer_history",
"card_linking"
],
"planner_reasoning": "This is a debit card request. First check customer history to confirm the customer relationship and whether identity/KYC is already established, then perform card linking to verify the checking account and link the debit card.",
"decision": "APPROVED",
"customer_message": "Your debit card request has been approved.",
"reasons": [
"All required checks passed."
],
"step_results": {
"customer_history": {
"customer_id": "CUST-9002",
"status": "PASS",
"existing_customer": true,
"relationship_years": 3.5,
"prior_accounts": 1,
"kyc_status": "CURRENT"
},
"card_linking": {
"customer_id": "CUST-9002",
"status": "PASS",
"account_verified": true,
"notes": "Account ACC-99887766 verified and eligible for debit card linking."
}
}
}
Другій запрос паўтарае іншы працэс, а супакоўваючы скрыпт stop_agent.sh выключае все:
# This all that we need to kill our agents - stop_agent.sh
pkill -f "uv run agents/"
pkill -f "orchestrator_langgraph_llm.py"
chmod a+x ./stop_agent.sh
# Execute stop agent
./stop_agents.sh
INFO: Shutting down
INFO: Shutting down
INFO: Shutting down
INFO: Shutting down
INFO: Shutting down
INFO: Shutting down
INFO: Shutting down
(account-opening-agents) krishnansriram@Krishnans-MacBook-Pro account-opening-agents % INFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [22185]
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Waiting for application shutdown.
INFO: Finished server process [22186]
INFO: Application shutdown complete.
INFO: Finished server process [22190]
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [22182]
INFO: Waiting for application shutdown.
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Application shutdown complete.
INFO: Finished server process [22184]
INFO: Finished server process [22187]
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [22188]
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [22183]
Агенты працуюць локальна, але ўсё ж застаўся шмат работы. Далей ім неабходна працаваць праз AgentGateway, а таксама з вікорыстанням службы рэйстры.
Развёртыванне ў кластэрэ
Пачніце з іменавання простору для всіх агентаў, ўсёлякія гэтыя іменавання спрыяюць лёгшам выкарыстанню ў будучыні. Ёго называюць banking-agents:
kubectl get namespace banking-agents 2>/dev/null || kubectl create namespace banking-agents
Адзін Dockerfile для кожнага агента
Існуюць два Dockerfile: адзін для агентаў прыемліцеля праграм, і адзін для рэжыстры і оркестрабірача. Паколькі кожны агент — гэта звычная прыемліцель праграм FastAPI з аднаковай структурой, адна файл Dockerfile.agent падходзіць для ўсіх іх:
FROM python:3.13-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-install-project
COPY agents ./agents
ARG APP_MODULE
ENV APP_MODULE=${APP_MODULE}
CMD uv run python ${APP_MODULE}
Размешчанне агентаў по аднаму
Цяпер паказана ўсёе процедура для першага агента, вакальніка, пасля чаго для кожнага з іншых выкананы тыя ж тры каманды:
docker build -t identity-agent:v1 \
--build-arg APP_MODULE=agents/identity_agent.py \
-f Dockerfile.agent .
kind load docker-image identity-agent:v1 --name agentgateway
kubectl apply -f- <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: identity-agent
namespace: banking-agents
spec:
replicas: 1
selector:
matchLabels: {app: identity-agent}
template:
metadata:
labels: {app: identity-agent}
spec:
containers:
- name: identity-agent
image: identity-agent:v1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 9001}]
---
apiVersion: v1
kind: Service
metadata:
name: identity-agent
namespace: banking-agents
spec:
selector: {app: identity-agent}
ports: [{port: 80, targetPort: 9001}]
type: ClusterIP
EOF
kubectl rollout status deploy/identity-agent -n banking-agents --timeout=60s
Шаблон павтараецца для всіх восьміх агентоў, які працуюць на портах 9001 да 9008 і абрабатваюць інформацыю пра історыю кліента, пераканальванне бакграунду, фінансавуюцю спроможнасць, дакументы з публічных базах дадзеных, ліміт крэдыту, прыўязку картак і супавядомленасць з правіламі подарунковых картак. Кожны раз зменшаюцца толькі імя, атракцыя і порт. Пасля чаго трэба падтвердзіць кантэйнеры:
kubectl get pods -n banking-agents
NAME READY STATUS RESTARTS AGE
background-check-agent-566d595b77-h69zg 1/1 Running 0 19h
card-linking-agent-786f4899-96s6c 1/1 Running 0 19h
credit-limit-agent-7fb6db7b76-b8zzf 1/1 Running 0 19h
customer-history-agent-b765d95fd-2pzh5 1/1 Running 0 19h
financial-capability-agent-5c879c8879-98sgp 1/1 Running 0 19h
gift-card-compliance-agent-75c55c74d4-qzpx4 1/1 Running 0 19h
identity-agent-668c66fff8-stgxt 1/1 Running 0 19h
public-records-agent-66dfd78c8b-qrcvh 1/1 Running 0 19h
Усе васьмыя пункты должны паказваць 1/1 Running.
Размешчэнне рэестру
Сервіс рэестру аб’яўляеся з кожным іншым сервісам у сістэме. Даколкі ён не запусцены, агенты не маюць інфармацыі пра тое, як з’яўляцца ў звязку. Пазней доступ да яго трэба захаваць, каб да яго моглі дасягнуць толькі агенты прыкладання, чым будзе забезпечана безпека агентоў і будзе створэны ясны спосаб прыемкі межу імі.
docker build -t agent-registry:v1 --build-arg APP_MODULE=agent_registry_service.py -f Dockerfile .
kind load docker-image agent-registry:v1 --name agentgateway
kubectl apply -f- <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent-registry
namespace: banking-agents
spec:
replicas: 1
selector:
matchLabels: {app: agent-registry}
template:
metadata:
labels: {app: agent-registry}
spec:
containers:
- name: agent-registry
image: agent-registry:v1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 9100}]
---
apiVersion: v1
kind: Service
metadata:
name: agent-registry
namespace: banking-agents
spec:
selector: {app: agent-registry}
ports: [{port: 80, targetPort: 9100}]
type: ClusterIP
EOF
kubectl rollout status deploy/agent-registry -n banking-agents --timeout=60s
kubectl port-forward -n banking-agents svc/agent-registry 9100:80 &
Пераканаўцеся, чы можа ён дасягнуць іншых восьмі агентаў праз назву DNS. Адпаведзь ёсцца картаю всіх службаў прыкладнага програмнага забезпечэння, якая выступае як рэестр, ў яком зберагліся месцазнаходжэння і таксама апісанні, што ідэальна стварае падставы для выкарыстоўвання спецыяльных навыкоў:
curl -s http://localhost:9100/agents | python3 -m json.tool
#Execution results
Handling connection for 9100
{
"agents": [
{
"name": "identity",
"url": "http://identity-agent.banking-agents.svc.cluster.local:80",
"description": "Verifies a customer's identity documents (passport, license, etc). Needed any time a NEW customer's identity hasn't already been established.",
"input_schema": {
"customer_id": "customer_id",
"document_type": "document_type",
"document_number": "document_number"
}
},
{
"name": "customer_history",
"url": "http://customer-history-agent.banking-agents.svc.cluster.local:80",
"description": "Looks up existing relationship, prior accounts, and KYC status for a customer. Useful to check whether identity verification can be skipped for an existing customer.",
"input_schema": {
"customer_id": "customer_id"
}
},
{
"name": "background_check",
"url": "http://background-check-agent.banking-agents.svc.cluster.local:80",
"description": "Criminal record, sanctions, and watchlist screening. Required for opening a new deposit account; usually not required for issuing a card to an already-verified customer.",
"input_schema": {
"customer_id": "customer_id",
"full_name": "full_name",
"date_of_birth": "date_of_birth"
}
},
{
"name": "financial_capability",
"url": "http://financial-capability-agent.banking-agents.svc.cluster.local:80",
"description": "Verifies income and estimates a credit score. Required for account opening and for credit card applications; not required for debit or gift cards.",
"input_schema": {
"customer_id": "customer_id",
"declared_annual_income": "declared_annual_income"
}
},
{
"name": "public_records",
"url": "http://public-records-agent.banking-agents.svc.cluster.local:80",
"description": "Checks address verification, litigation, and bankruptcy history. Required for opening a new deposit account.",
"input_schema": {
"customer_id": "customer_id",
"address": "address"
}
},
{
"name": "credit_limit",
"url": "http://credit-limit-agent.banking-agents.svc.cluster.local:80",
"description": "Determines an approved credit limit based on income and the requested limit. Required ONLY for credit card applications.",
"input_schema": {
"customer_id": "customer_id",
"declared_annual_income": "declared_annual_income",
"requested_credit_limit": "requested_credit_limit"
}
},
{
"name": "card_linking",
"url": "http://card-linking-agent.banking-agents.svc.cluster.local:80",
"description": "Verifies a bank account to link a debit card to. Required ONLY for debit card applications.",
"input_schema": {
"customer_id": "customer_id",
"linked_account_number": "linked_account_number"
}
},
{
"name": "gift_card_compliance",
"url": "http://gift-card-compliance-agent.banking-agents.svc.cluster.local:80",
"description": "Checks a gift card purchase amount against AML limits. Required ONLY for gift card purchases.",
"input_schema": {
"customer_id": "customer_id",
"purchase_amount": "purchase_amount"
}
}
]
}
Развяроцьба оркестратора
Кранты Azure OpenAI падаюць як Секрэт і ніколі не ўключаюцца ў адпаведны адбітак:
kubectl create secret generic azure-openai-secret \
-n banking-agents \
--from-literal=AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com" \
--from-literal=AZURE_OPENAI_DEPLOYMENT="gpt-5-1-chat" \
--from-literal=AZURE_OPENAI_API_KEY="your-key" \
--from-literal=AZURE_OPENAI_API_VERSION="2025-04-14"
Потым створыце і разгружыце адпраўку, следуячы там самым прынцыпам, якія існуюць для всіх іншых служб:
docker build -t orchestrator:v1 --build-arg APP_MODULE=orchestrator_dynamic.py -f Dockerfile .
kind load docker-image orchestrator:v1 --name agentgateway
kubectl apply -f- <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: orchestrator
namespace: banking-agents
spec:
replicas: 1
selector:
matchLabels: {app: orchestrator}
template:
metadata:
labels: {app: orchestrator}
spec:
containers:
- name: orchestrator
image: orchestrator:v1
imagePullPolicy: IfNotPresent
ports: [{containerPort: 9000}]
env:
- name: AGENT_REGISTRY_URL
value: "http://agent-registry.banking-agents.svc.cluster.local:80"
envFrom:
- secretRef:
name: azure-openai-secret
---
apiVersion: v1
kind: Service
metadata:
name: orchestrator
namespace: banking-agents
spec:
selector: {app: orchestrator}
ports: [{port: 80, targetPort: 9000}]
type: ClusterIP
EOF
kubectl rollout status deploy/orchestrator -n banking-agents --timeout=60s
kubectl port-forward -n banking-agents svc/orchestrator 9000:80 &
Час для тэста:
curl -s -X POST http://localhost:9000/process-enquiry \
-H "Content-Type: application/json" \
-d '{
"enquiry_text": "I would like to purchase a $500 gift card",
"customer_id": "CUST-7001", "full_name": "Jordan Smith", "date_of_birth": "1990-04-12",
"document_type": "passport", "document_number": "X1234567",
"declared_annual_income": 95000, "address": "123 Main St, Columbus, OH",
"purchase_amount": 500
}' | python3 -m json.tool
Handling connection for 9000
{
"customer_id": "CUST-7001",
"product_type": "gift_card",
"planned_steps": [
"gift_card_compliance"
],
"planner_reasoning": "This is a gift card purchase, so the only relevant specialist is gift_card_compliance to check the amount against AML limits. No identity, credit, account, or background screening steps are needed for this request.",
"decision": "APPROVED",
"customer_message": "Your gift card request has been approved.",
"reasons": [
"All required checks passed."
],
"step_results": {
"gift_card_compliance": {
"customer_id": "CUST-7001",
"status": "PASS",
"within_aml_limit": true,
"notes": "Purchase amount 500.0 vs AML threshold 2000.0."
}
}
}
У кластары зараз працюе функцыйнальная сістэма з мнагамі агентамі з оркестрактаром, што ўжо далёка ад простага канцэнтрабу.
Размешчэнне оркестрактара за гейтвэем
Чаму взагалі размешчаць оркестрактар за гейтвэем? Ёсць пяць прычын:
- Адны стабільны пункт входу. Рэальны URL, такі як
https://your-domain/orchestrator, заменяе тунель, які можа быць выкарыстоўваны толькі адміністратаром Kubernetes. Будзь-які рэальны кліент, ад мобільнага бэкенда да іншага внутрашняга сервісу чы ўзаемадзейнаўання з партнерам, можа звычайна да яго дасягнуць. - Автанація ранейш за выконанне коду. За дапамогою механізма JWT і політыкы RBAC, якія выкарыстоўваюцца для забезпечэння, непрааверыжаны чы неуповажнены апытчык засланаецца на гейтвэе і ніколі не трапляецца да логікі прыемлень аплікацыі оркестрактара.
Ранейшыя банкавыя серверы MCP выказвалі тую ж думку ў зв’язку з інструментамі. Гэты аргумент паднімаеся на вышэйшы рэвэл, адколі вхідныя дзверы цэлай багатоагентнай сістэмы заслуговуюць на тое ж управлінне, якое і адзінкі агенты.
Просты ўраджэннык HTTP, такі як оркестрацыянт, без перакладу MCP і без налашоўкаў прадаўця AI, не патрэбуе AgentgatewayBackend. HTTPRoute можа быць наставлены безпосерэдна на Service Kubernetes. Жыўе ўсё ж адна проблема: Service оркестрацыянта знаходзіцца ў banking-agents, а HTTPRoute — у agentgateway-system, тое ў разным пространстве імен. API гэтага шлюза выкарыстоўвае явны ReferenceGrant у цэльвым пространстве імен, перш чым разрашаць звязак між пространствамі імен.
Спачатку трэба разрышыць звязак між пространствамі імен:
kubectl apply -f- <<EOF
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-agentgateway-to-orchestrator
namespace: banking-agents
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: agentgateway-system
to:
- group: ""
kind: Service
name: orchestrator
EOF
Другае – перадавайце запыт прымо да Сэрвісу:
kubectl apply -f- <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: banking-orchestrator-route
namespace: agentgateway-system
spec:
parentRefs:
- name: agentgateway-proxy
rules:
- matches:
- path: {type: PathPrefix, value: /orchestrator}
backendRefs:
- name: orchestrator
namespace: banking-agents
port: 80
EOF
kubectl get httproute banking-orchestrator-route -n agentgateway-system
Потым пераканацце праз гэйтвэй:
curl -s -X POST http://localhost:8080/orchestrator/process-enquiry \
-H "Content-Type: application/json" \
-d '{
"enquiry_text": "I would like to purchase a $500 gift card",
"customer_id": "CUST-7001", "full_name": "Jordan Smith", "date_of_birth": "1990-04-12",
"document_type": "passport", "document_number": "X1234567",
"declared_annual_income": 95000, "address": "123 Main St, Columbus, OH",
"purchase_amount": 500
}' | python3 -m json.tool
# Execution Results
Handling connection for 8080
{
"customer_id": "CUST-7001",
"product_type": "gift_card",
"planned_steps": [
"gift_card_compliance"
],
"planner_reasoning": "This enquiry is for a gift card purchase. The only required specialist agent is gift_card_compliance to check the $500 amount against AML limits; no identity, background, or account-linking checks are needed for a straightforward gift card purchase.",
"decision": "APPROVED",
"customer_message": "Your gift card request has been approved.",
"reasons": [
"All required checks passed."
],
"step_results": {
"gift_card_compliance": {
"customer_id": "CUST-7001",
"status": "PASS",
"within_aml_limit": true,
"notes": "Purchase amount 500.0 vs AML threshold 2000.0."
}
}
}
# 2nd Execution
curl -s http://localhost:8080/orchestrator/health
Handling connection for 8080
# Execution results
{"agent":"dynamic-banking-orchestrator","status":"ok","registry_url":"http://agent-registry.banking-agents.svc.cluster.local:80"}
Вбудованыя меры захастання і ліміты частоты
Вбудованыя фільтры AgentGateway называюцца мерамі захастання або захамленнямі запытоў. Яны стосуюцца кантэнту трафіку LLM, а не загальных пакетаў дадзеных у фармаце REST, таму яны прымаюцься на маршруце /llm, у з’яўленні з Foundry, але не для звычнага JSON-файлу оркестратора.
Явным рашэннем для банка ёсць маскаванне персональных дадзеных на маршруце LLM. Оркестратор адправляе дакладнасці кліента, такія як імена, даты нараджэння, номеры дакументаў, дохады і адрэсы, у модэль планавання ў рамках запыту. Аднак у нынешнім варыянцы коду агента не вікорыстоўваецца маршруц /llm для всіх запытоў да модэля. Гэтты шлюз праставляе готовыя меры захастання для чулых дадзеных на гэтым маршруце, але гэтае рэалізаванне адправляе запыты да модэля праз маршруц у стылі REST, таму гэтыя захопы ніколі не актывацуюцца. Правільным рашэннем ёсць адправка запытоў да LLM праз /llm, пры чым такая змяна яшчэ не адбылася.
Лімітаванне калькулявання — гэта загальная політыка трафіку, якая практычна безпосерэдньа да маршруту оркестрабіста:
kubectl apply -f- <<EOF
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayPolicy
metadata:
name: banking-orchestrator-ratelimit
namespace: agentgateway-system
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: banking-orchestrator-route
traffic:
rateLimit:
local:
- requests: 60
unit: Minutes
burst: 10
EOF
Належна burst: 10 ўстановка ёсць необавязковай, але корыстной. Яна дазволяе короткія падвышэння колькасці запітоў вышэй за стаўкую 60 запытоў на хвіліну, што дапамагае законным кліентам, якія адправляюць кальколькі запытоў паспела, не змянюючы стаўкавага ліміту. Пераканайцеся, што застосоўана відпаведная політыка:
kubectl get agentgatewaypolicy banking-orchestrator-ratelimit -n agentgateway-system
NAME ACCEPTED ATTACHED AGE
banking-orchestrator-ratelimit True True 62s
Тады спробуйце:
for i in $(seq 1 75); do
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/orchestrator/health
done | sort | uniq -c
# Execution results
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
............................
............................
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
70 200
5 429
Прыгаючы прыблізна першыя 70 запітоў, 60 стабільных плюс дазволена кантытатыва частка, должны вярнуць 200, а пасля таго, у тым жа прызначанні хвілі, — 429. Якшто усё вярнуе 200, значыць правіла не былі належна прыўязаны; у такім случае команда kubectl describe agentgatewaypolicy banking-orchestrator-ratelimit -n agentgateway-system пакажае будзь-якія праблемы з распазнаванням targetRef, але можа таксама значыць, што 75 запітоў проста не выкарысталі дазволеную кантытатыва частку достатньа швядка. Падняць кантытатыва запітоў да 100 рашае гэтыя проблемы.
Гэтам выканана рэалізацыя на аднойчасных агентах. Што застаецца — гэта забезпечыць доступ і дадаць правіла керавання межу самымі агентамі.