Zum Hauptinhalt springen
connic

SQS-Bestellverarbeitung

Von connic
E-CommerceFortgeschritten

Bestellungen aus SQS-Queues werden mit Betrugserkennung, Bestandsprüfung und Concurrency-Keys validiert und abgewickelt. Ergebnisse werden in einer ausgehenden Queue veröffentlicht.

Mit der CLI installieren
$ connic init my-project \
  --templates=sqs-order-processor
Publisher
Von connic
Schwierigkeitsgrad
Fortgeschritten
Funktioniert mit
AWS SQS
Quelle
GitHub

Was das Template macht

Die Pipeline verarbeitet E-Commerce-Bestellungen aus einer SQS-Queue und übernimmt Validierung sowie Auftragsabwicklung. Der Validator prüft die Vollständigkeit, berechnet Summen unabhängig, kontrolliert den Bestand und markiert Betrugssignale. Bestellungen mit derselben customer_id werden nacheinander verarbeitet, um Duplikate zu verhindern. Middleware weist fehlerhafte Payloads mit StopProcessing ab, bevor das LLM ausgeführt wird. Für genehmigte Bestellungen erzeugt ein deterministischer Tool-Agent Datensätze zur Auftragsabwicklung. Die Ergebnisse werden in einer ausgehenden SQS-Queue veröffentlicht.

Architektur
SQS (Bestellungen)
order-pipeline
order-validator
order-fulfiller
SQS (Auftragsabwicklung)
Bestellvalidierung
Eingehende Bestellungen werden vor der Auftragsabwicklung auf fehlende Felder, Rechenfehler und Betrugssignale geprüft.
Bestandsmanagement
Die Validierung prüft den Lagerbestand und lehnt Bestellungen für nicht verfügbare Artikel ab.
E-Commerce-Backend
Die Pipeline verarbeitet große Bestellwarteschlangen, steuert die gleichzeitige Ausführung je Kunde und hält feste Zeitlimits ein.

Jede Datei landet in deinem Projekt

connic init kopiert diese Dateien in deine Codebase. Dort änderst du sie wie den Rest deines Codes.

agents/sqs-order-processor/
tools/
middleware/
schemas/
tests/
agents/sqs-order-processor/order-fulfiller.yaml

Tool-Agent, der eine Python-Funktion aufruft, ohne Modell-Tokens.

version: "1.0"

name: order-fulfiller
type: tool
description: "Creates a fulfillment record for validated orders - deterministic, no LLM"
tool_name: order_tools.create_fulfillment
agents/sqs-order-processor/order-pipeline.yaml

Führt seine Agenten nacheinander aus, jeden mit der Ausgabe des vorherigen.

version: "1.0"

name: order-pipeline
type: sequential
description: "Validate order, then create fulfillment record"

agents:
  - order-validator
  - order-fulfiller
agents/sqs-order-processor/order-validator.yaml

LLM-Agent mit Modell, Prompt und Tools.

version: "1.0"

name: order-validator
type: llm
model: connic/gpt-5.6-luna  # or gemini/gemini-3.5-flash with Gemini BYOK configured
# Different provider so queued orders keep processing during a primary outage.
fallback_model: anthropic/claude-sonnet-5
description: "Validates incoming orders from SQS for completeness, fraud signals, and business rules"

system_prompt: |
  You validate e-commerce orders received from an SQS queue.

  If _sqs.approximate_receive_count > 1, this is a retry - the order may have
  been partially processed before. Note this in the validation output.

  Validation checks:
  1. Completeness: all required fields present (customer_id, items, shipping_address,
     payment_method). Flag missing fields.
  2. Business rules:
     - Order total must match sum of (item_price * quantity) + shipping + tax
     - Maximum 50 items per order
     - Maximum order value: 10,000 (currency units)
     - Shipping address must include country, city, postal code
  3. Fraud signals:
     - Multiple high-value orders from same customer in short period
     - Shipping address different from billing address (flag, don't reject)
     - Known test card numbers or suspicious patterns
  4. Use order_tools.calculate_order_total to independently verify the math.
  5. Use order_tools.check_inventory to verify stock availability.

  Output a validation result: approved, needs_review, or rejected.
  For rejected orders, include specific reasons.
  For needs_review, include what needs human attention.

temperature: 0.1
output_schema: order-validation
timeout: 30

concurrency:
  key: "customer_id"
  on_conflict: queue

tools:
  - order_tools.calculate_order_total
  - order_tools.check_inventory
tools/order_tools.py

Python-Funktionen, die die Agenten als Tools aufrufen.

"""Order processing tools for validation and fulfillment."""

import uuid
import time
import zlib
from typing import Any


def calculate_order_total(
    items: list[dict[str, Any]],
    shipping_cost: float = 0.0,
    tax_rate: float = 0.0,
) -> dict[str, Any]:
    """Calculate the expected order total from line items.

    Independently verifies order math so the LLM can detect discrepancies
    with the stated total.

    Args:
        items: List of items, each with "price" and "quantity" keys
        shipping_cost: Shipping fee
        tax_rate: Tax rate as decimal (e.g. 0.19 for 19%)

    Returns:
        Dict with subtotal, tax, shipping, and total breakdown
    """
    subtotal = sum(
        item.get("price", 0) * item.get("quantity", 1)
        for item in items
    )
    tax = round(subtotal * tax_rate, 2)
    total = round(subtotal + tax + shipping_cost, 2)

    return {
        "subtotal": round(subtotal, 2),
        "tax": tax,
        "shipping": shipping_cost,
        "total": total,
        "item_count": sum(item.get("quantity", 1) for item in items),
    }


def check_inventory(
    items: list[dict[str, Any]],
) -> dict[str, Any]:
    """Check inventory availability for all order items.

    Args:
        items: List of items with "sku" and "quantity" keys

    Returns:
        Dict with all_available flag and per-item availability
    """
    availability = []
    all_available = True

    for item in items:
        sku = item.get("sku", "unknown")
        quantity = item.get("quantity", 1)
        available = True
        # Deterministic per-SKU stock (crc32, not hash() which varies per process).
        stock = 50 + zlib.crc32(sku.encode()) % 200

        if stock < quantity:
            available = False
            all_available = False

        availability.append({
            "sku": sku,
            "requested": quantity,
            "in_stock": stock,
            "available": available,
        })

    return {
        "all_available": all_available,
        "items": availability,
    }


def create_fulfillment(
    payload: dict[str, Any] | None = None,
    context: dict[str, Any] | None = None,
) -> dict[str, Any]:
    """Create a fulfillment record for an approved order.

    Runs as a tool agent after order-validator, so it receives the validation
    result as its payload. Only creates a record if the order was approved;
    rejected or needs_review orders are returned with instructions.

    Args:
        payload: The validation result, with keys: order_id, validation_status
            (approved, needs_review, or rejected), items, and shipping_address.
        context: Auto-injected run context.

    Returns:
        Fulfillment record or rejection notice
    """
    payload = payload or {}
    order_id = payload.get("order_id")
    validation_status = payload.get("validation_status")
    items = payload.get("items")
    shipping_address = payload.get("shipping_address")

    if validation_status != "approved":
        return {
            "fulfilled": False,
            "order_id": order_id,
            "reason": f"Order status is '{validation_status}', not approved",
            "action": "manual_review" if validation_status == "needs_review" else "notify_customer",
        }

    return {
        "fulfilled": True,
        "fulfillment_id": str(uuid.uuid4()),
        "order_id": order_id,
        "status": "pending_shipment",
        "items_to_pick": len(items) if items else 0,
        "shipping_address": shipping_address,
        "created_at": time.time(),
    }
middleware/order-validator.py

Läuft vor und nach dem gleichnamigen Agenten.

"""Middleware for order-validator.

Extracts order metadata into context and rejects obviously invalid payloads
before the LLM processes them.
"""

import json
from typing import Any

from connic import StopProcessing


async def before(content: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]:
    """Extract order metadata and reject empty/malformed orders."""
    parts = content.get("parts", [])
    for part in parts:
        if isinstance(part, dict) and "text" in part:
            try:
                payload = json.loads(part["text"])
                if "order_id" not in payload:
                    raise StopProcessing(
                        '{"error": "Missing order_id", "status": "rejected"}'
                    )
                context["order_id"] = payload["order_id"]
                context["customer_id"] = payload.get("customer_id", "unknown")

                items = payload.get("items", [])
                if not items:
                    raise StopProcessing(
                        '{"error": "Order has no items", "status": "rejected", '
                        f'"order_id": "{payload["order_id"]}"' + '}'
                    )
            except json.JSONDecodeError:
                raise StopProcessing(
                    '{"error": "Invalid JSON payload", "status": "rejected"}'
                )
    return content


async def after(response: str, context: dict[str, Any]) -> str:
    return response
schemas/order-validation.json

JSON-Schema für die strukturierte Ausgabe eines Agenten.

{
  "type": "object",
  "description": "Order validation result",
  "properties": {
    "order_id": {
      "type": "string"
    },
    "validation_status": {
      "type": "string",
      "enum": ["approved", "needs_review", "rejected"],
      "description": "Overall validation decision"
    },
    "calculated_total": {
      "type": "number",
      "description": "Independently calculated order total"
    },
    "stated_total": {
      "type": "number",
      "description": "Total claimed in the order"
    },
    "total_matches": {
      "type": "boolean",
      "description": "Whether calculated and stated totals match"
    },
    "inventory_available": {
      "type": "boolean",
      "description": "Whether all items are in stock"
    },
    "issues": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "type": {"type": "string", "enum": ["missing_field", "business_rule", "fraud_signal", "inventory"]},
          "severity": {"type": "string", "enum": ["info", "warning", "error"]},
          "message": {"type": "string"}
        },
        "required": ["type", "severity", "message"]
      },
      "description": "Validation issues found"
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "sku": {"type": "string"},
          "quantity": {"type": "integer"},
          "price": {"type": "number"}
        }
      }
    },
    "shipping_address": {
      "type": "object",
      "properties": {
        "country": {"type": "string"},
        "city": {"type": "string"},
        "postal_code": {"type": "string"}
      }
    }
  },
  "required": ["order_id", "validation_status"]
}
tests/order-pipeline.yaml

1 Testfall für connic test.

version: "1.0"

# End-to-end through the sequential pipeline: validator approves, then the
# order-fulfiller tool agent creates the fulfillment record. The validator's
# tools are mocked (deterministic approval); create_fulfillment is pure and
# runs for real, so the final output is the genuine fulfillment record.
defaults:
  runs: 1
  timeout_s: 90
  mocks: order_mocks

tests:
  - name: clean_order_is_fulfilled
    payload: '{"order_id": "A200", "customer_id": "c9", "payment_method": "credit_card", "items": [{"sku": "SKU1", "price": 25, "quantity": 1}], "shipping_address": {"country": "DE", "city": "Berlin", "postal_code": "10115"}, "total": 25}'
    expected_result: output.fulfilled == true and output.order_id == "A200"
tests/order-validator.yaml

3 Testfälle für connic test.

version: "1.0"

# Custom tools mocked so nothing is written and no real inventory service is
# touched. strict_mocks guarantees the validator never reached for a tool we did
# not stub (it only calls custom tools, so this holds).
defaults:
  runs: 1
  timeout_s: 60
  mocks: order_mocks
  strict_mocks: true

tests:
  - name: approves_clean_order
    payload: '{"order_id": "A123", "customer_id": "c1", "payment_method": "credit_card", "items": [{"sku": "SKU1", "price": 10, "quantity": 2}], "shipping_address": {"country": "DE", "city": "Berlin", "postal_code": "10115"}, "total": 20}'
    expected_result: output.validation_status == "approved"
    expected_tool_calls:
      - order_tools.calculate_order_total
      - order_tools.check_inventory

  - name: flags_total_mismatch
    payload: '{"order_id": "A124", "customer_id": "c1", "items": [{"sku": "SKU1", "price": 10, "quantity": 2}], "shipping_address": {"country": "DE", "city": "Berlin", "postal_code": "10115"}, "total": 999}'
    expected_result: output.validation_status != "approved"
    expected_tool_calls:
      - order_tools.calculate_order_total

  - name: rejects_empty_order_in_middleware
    # The before() middleware short-circuits empty orders before the LLM runs,
    # so no validation tools are called.
    payload: '{"order_id": "A125", "customer_id": "c1", "items": []}'
    expected_result: output.status == "rejected"
    expected_no_tool_calls:
      - order_tools.calculate_order_total
      - order_tools.check_inventory

Installieren und bereitstellen

Installiere das Template, erstelle ein connic-Projekt und starte das Deployment über ein verbundenes Git-Repository oder die CLI.

Voraussetzungen
  • Python 3.10+
  • Ein connic-Konto mit einem angelegten Projekt
  • Projektguthaben für ein connic/* KI-Modell oder Zugangsdaten für einen BYOK Provider
Deployment mit Git
Verbinde das Repository unter Projekt Settings → Git & Environments, ordne einen Branch einem Environment zu und pushe.
Deployment mit der CLI
Erstelle unter Projekt Settings → API Keys & MCP Auth einen API Key, führe im Projektordner connic login aus und danach connic deploy. Änderungen probierst du vorher mit connic dev und connic test aus.
Verbinden und konfigurieren
Füge eine eingehende SQS-Verbindung für die Bestell-Queue und eine ausgehende SQS-Verbindung für die Fulfillment-Queue hinzu. Hinterlege für beide AWS-Zugangsdaten. Ein connic/* KI-Modell nutzen oder BYOK-Zugangsdaten unter Projekt Settings → LLM Provider hinterlegen.
~/my-project
SDK
$ pip install connic-composer-sdk
Projekt
$ connic init my-project --templates=sqs-order-processor
connic Init
──────────────────────────────
→ Created directory: my-project
→ Creating project structure...
✓ agents/, tools/, middleware/, schemas/
→ Fetching templates from connic-awesome-agents...
✓ Fetched
→ Merging templates...
✓ Added template: sqs-order-processor
→ Next steps:
1. Run `connic lint` to validate your project
2. Run `connic dev` to choose a dev environment with hot reload
3. Run `connic deploy` when ready
✓ Initialized with templates: sqs-order-processor
Deploy
$ connic deploy
→ Validating project files...
✓ Lint passed: 3 agent(s) validated (order-validator, order-fulfiller, order-pipeline)
✓ Lint passed
✓ Deployment created.

Weitere Agent Templates

Zum Marketplace