Головна / Статті

Банківські процеси з багатьма агентами на Kubernetes: LangGraph, реєстр та AgentGateway

Створіть спеціалізованих агентів FastAPI, оркестратора LangGraph із планувальником на основі LLM, розгорніть їх у кластері з реєстром, а потім додайте маршрутизацію та обмеження швидкості за допомогою AgentGateway.

Видавець: REACTAPP.TOP17 хв читання3826 слів

Джерела: agentgateway in the world of Agentic developement

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)

Екзекутор став одним вузлом, який проходить по будь-якому плану, створеному ШІ, і більше не дотримується фіксованої послідовності країв графа. Його перевіряли безпосередньо, обійшовши справжній виклик ШІ, оскільки це вимагає актуальних облікових даних 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, реалізованій у рамках заходів з безпеки, непідтверджений чи неуповноважений користувач відхиляється на рівні гейтвею та ніколи не потрапляє до логіки додатку оркестратора.
  • Обмеження швидкості. Клієнт, який поводиться неналежним чином, або помилка в якомусь верхньому компоненті, може заповнити оркестратор запитами, і кожен з них потрапляє до моделі планування та збільшує рахунок Foundry. Гейтвей може це обмежити.
  • Одне місце для перегляду всього трафіку. Логування та можливості моніторингу для кожного запиту надходять від гейтвею, а не від інструментів всередині оркестратора.
  • Стабільна структура під час змін. Заміна версій оркестратора, додавання репліки чи переїзд на інший сервіс не впливають на користувачів, оскільки вони завжди знали лише шлях до гейтвею.
  • Ранні банківські сервери MCP також наголошували на цьому щодо інструментів. Цей аргумент посилюється ще більше, адже вхідні точки всієї багатоагентної системи заслуговують на такий самий рівень керування, як і окремі агенти.

    Простий HTTP-бекенд, такий як оркестратор, без підтримки MCP та без налаштувань постачальника ШІ, не потребує AgentgatewayBackend. HTTPRoute може безпосередньо вказувати на Kubernetes-Service. Однак залишається одна проблема: 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 вирішить цю проблему.

    Цим завершується реалізація агентської системи. Залишається лише забезпечити безпечний доступ та додати механізми контролю між самими агентами.