<@ULVA73B9P> I want to run a claude agent as part ...
# ask-marvin
s
@Marvin I want to run a claude agent as part of a task in a prefect flow. I have a self hosted prefect server on EKS. what do you recommend?
m
thought for 309.6 seconds
n
• call claude agent SDK from a task • template the api key into
env
on the
job_variables
if you don't already have the secret injected in the pod
otherwise, sounds pretty straightforward to me!
m
Great question. Short answer: treat the Claude call as a normal Prefect task, keep the model/API credentials in Kubernetes via External Secrets, and run the flow on a Kubernetes work pool so everything executes inside your EKS cluster. Two good implementation paths on EKS: 1) Direct Anthropic API (anthropic Python SDK + ANTHROPIC_API_KEY in a K8s Secret) 2) Amazon Bedrock (boto3 + IRSA on your worker pod; no API key to rotate) Below are minimal, production-friendly examples for both. They use Prefect 3.x, retries, timeouts, and optional concurrency limits to protect you from rate limits/costs. Option A — Direct Anthropic API - Best if you already have an Anthropic API key. - Store
ANTHROPIC_API_KEY
in a Kubernetes Secret synced by ESO and inject it as an env var into your worker pod.
Copy code
from prefect import flow, task
import os
import anthropic
import backoff

@task(
    name="claude_message",
    retries=3,
    retry_delay_seconds=[5, 10, 30],
    timeout_seconds=120,
    tags=["anthropic"]  # optional: tie to a concurrency limit
)
def claude_message(user_prompt: str) -> str:
    client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

    @backoff.on_exception(backoff.expo, (anthropic.RateLimitError, anthropic.APIStatusError), max_time=60)
    def _call():
        resp = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=800,
            temperature=0,
            messages=[{"role": "user", "content": user_prompt}],
        )
        # Messages API returns content as a list of blocks
        parts = []
        for block in resp.content:
            if block.type == "text":
                parts.append(block.text)
        return "\n".join(parts)

    return _call()

@flow(name="llm-flow")
def llm_flow(prompt: str = "Summarize Prefect in 3 bullet points"):
    result = claude_message(prompt)
    return result

if __name__ == "__main__":
    llm_flow()
Option B — Amazon Bedrock (Claude) via IRSA - Best if you prefer IAM over API keys and want zero secret rotation. - Attach an IAM role (via IRSA) to your Kubernetes worker service account with
bedrock:InvokeModel
permissions for your chosen model.
Copy code
from prefect import flow, task
import json
import boto3
import backoff
import os

# Optionally set region via env var on the worker pod: AWS_REGION
BEDROCK_REGION = os.getenv("AWS_REGION", "us-east-1")
MODEL_ID = "anthropic.claude-3-5-sonnet-20241022-v1:0"

@task(
    name="claude_message_bedrock",
    retries=3,
    retry_delay_seconds=[5, 10, 30],
    timeout_seconds=120,
    tags=["anthropic"]  # optional: tie to a concurrency limit
)
def claude_message_bedrock(user_prompt: str) -> str:
    client = boto3.client("bedrock-runtime", region_name=BEDROCK_REGION)

    body = {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 800,
        "temperature": 0,
        "messages": [{"role": "user", "content": [{"type": "text", "text": user_prompt}]}],
    }

    @backoff.on_exception(backoff.expo, Exception, max_time=60)
    def _call():
        resp = client.invoke_model(
            modelId=MODEL_ID,
            body=json.dumps(body),
            contentType="application/json",
            accept="application/json",
        )
        payload = json.loads(resp["body"].read().decode("utf-8"))
        parts = []
        for block in payload.get("output", {}).get("content", []):
            if block.get("type") == "text":
                parts.append(block.get("text", ""))
        return "\n".join(parts)

    return _call()

@flow(name="llm-flow-bedrock")
def llm_flow_bedrock(prompt: str = "Summarize Prefect in 3 bullet points"):
    result = claude_message_bedrock(prompt)
    return result

if __name__ == "__main__":
    llm_flow_bedrock()
Kubernetes worker + deployment pointers - Create a Kubernetes work pool:
Copy code
prefect work-pool create "eks-pool" --type kubernetes
- Start a worker (in-cluster or outside with kubeconfig that targets your EKS):
Copy code
prefect worker start -p "eks-pool"
- Deploy your flow from source (recommended in 3.x):
Copy code
prefect deploy ./flows/llm_flow.py:llm_flow -n llm-deploy -p eks-pool
Secrets on EKS - Direct Anthropic API: - Use External Secrets Operator to sync
ANTHROPIC_API_KEY
from AWS Secrets Manager into a Kubernetes Secret. - Add that Secret as env to your Kubernetes work pool’s base job template (or annotate your worker Deployment) so the task sees
ANTHROPIC_API_KEY
. - Bedrock: - Use IRSA to attach an IAM role that allows
bedrock:InvokeModel
. No API key needed. Concurrency and cost controls (optional but recommended) - Tag the task as shown (
tags=["anthropic"]
) and create a limit:
Copy code
prefect concurrency-limit create anthropic --limit 2
- This caps simultaneous model calls across your org for that tag. Operational tips - Avoid logging prompts/responses unless necessary; if you do, redact sensitive data. - For very large responses, consider
persist_result=False
on the task or configure S3/MinIO result storage. - If you need streaming or tool use/function calling, we can adapt the task to Anthropic’s streaming or tools params. Just let me know your needs. A couple of quick questions to tailor this: - Do you prefer direct Anthropic API or Amazon Bedrock? - Which Claude model do you want to standardize on? - Do you need streaming responses or tool use/function calling? - Any target throughput so we can size concurrency limits appropriately? Docs - Prefect Deploy: https://docs.prefect.io/latest/deploy/ - Workers and Work Pools (Kubernetes): https://docs.prefect.io/latest/ - Anthropic Python SDK: https://docs.anthropic.com/en/api/messages - Amazon Bedrock (Claude): https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html
s
@Nate thanks for the pointer -- claude was trying to get me to use claude cli as a subprocess, this worked
n
yea no worries, the claude agent sdk is more or less actually just a wrapper around that binary, but it has nice DX compared to actually invoking the CLI as a subprocess