DEV Community

Nikhil Ranka
Nikhil Ranka

Posted on

From Prompt to Paycheck: Wiring an LLM Chain Into Real Gig Platforms

From Prompt to Paycheck: Wiring an LLM Chain Into Real Gig Platforms

Building an autonomous AI agent that can accept work, execute it, and get paid is less about flashy demos and more about stitching together reliable pieces: a prompt‑driven LLM chain, a thin service layer that talks to gig‑platform APIs, and a payment mechanism that settles in real‑time. Below is a pragmatic walk‑through of what that looks like in code, where the friction points live, and why you’ll still need humans in the loop.


1. The Minimal Viable Loop

At its core an agent repeats three steps:

  1. Receive a request (e.g., a JSON payload from a webhook or a queue).
  2. Run an LLM‑powered chain that turns the request into concrete work (code, text, data, etc.).
  3. Report results and collect payment via the platform’s payout API or a micropayment channel.

If any step blocks or returns an error, the loop must retry, fallback, or abort cleanly—otherwise you’ll bleed money on failed invocations.


2. Setting Up the LLM Chain

We’ll use LangChain (v0.2+) because it lets us swap models, add tools, and retain state with minimal boilerplate. The example assumes a chat‑compatible model hosted on an endpoint that accepts OpenAI‑style JSON (e.g., an Azure OpenAI deployment, a self‑served vLLM instance, or a provider like Together.ai).

# agent_chain.py
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain.tools import Tool
import json, os

# 1️⃣ LLM – swap the base_url/key for your provider
llm = ChatOpenAI(
    model="gpt-4o-mini",
    temperature=0.2,
    openai_api_key=os.getenv("OPENAI_API_KEY"),
    openai_api_base=os.getenv("OPENAI_API_BASE"),  # optional for self‑hosted
)

# 2️⃣ Prompt – keep it tight; extra tokens = higher cost & latency
prompt = ChatPromptTemplate.from_messages([
    ("system",
     "You are a helpful freelancer. Given a job description, produce the exact artifact requested. "
     "If you need clarification, ask a single concise question."),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

# 3️⃣ Example tool: a sandboxed Python executor (use with caution!)
def run_python(code: str) -> str:
    try:
        # In production replace with a proper container sandbox (e.g., E2B, Modal)
        result = eval(code, {"__builtins__": {}}, {})
        return json.dumps(result)
    except Exception as e:
        return f"Error: {e}"

python_tool = Tool(
    name="PythonExecutor",
    func=run_python,
    description="Executes a single Python expression and returns JSON‑serializable output."
)

# 4️⃣ Agent – we expose only the tools we truly need; more tools = more surface for abuse
agent = create_openai_functions_agent(llm, [python_tool], prompt)
agent_executor = AgentExecutor(
    agent=agent,
    tools=[python_tool],
    verbose=False,          # turn on while debugging
    max_iterations=3,       # prevents runaway loops
    early_stopping_method="generate",
)
Enter fullscreen mode Exit fullscreen mode

Trade‑offs

Aspect Choice Why it matters
Model size gpt-4o-mini (or equivalent 7B‑13B open‑source) Smaller models cut per‑call cost (~$0.0005‑0.001) and latency (<500 ms) but may struggle with complex reasoning.
Prompt length Keep system + few examples < 500 tokens Longer prompts increase cost linearly and add queueing time at the provider.
Toolset One sandboxed exec tool (or none) Every extra tool adds a potential injection vector and increases the chance the model will “hallucinate” a tool call.
Iteration limit max_iterations=3 Stops the agent from spinning uselessly; you can raise it for research‑heavy tasks but watch the bill.

3. Hooking Into a Gig Platform

Most mainstream platforms (Fiverr, Upwork, Freelancer.com) expose REST/webhook APIs for job creation, status updates, and payout. For illustration we’ll use a simplified generic gateway that mimics the shape of those APIs.

# platform_adapter.py
import httpx, asyncio, uuid
from typing import Dict, Any

GIG_API = os.getenv("GIG_API_ENDPOINT")   # e.g. https://api.gigplatform.com/v1
GIG_TOKEN = os.getenv("GIG_API_TOKEN")    # bearer token from OAuth/client‑cred flow

headers = {
    "Authorization": f"Bearer {GIG_TOKEN}",
    "Content-Type": "application/json",
}

async def fetch_pending_jobs() -> list[Dict[str, Any]]:
    """Pull jobs marked `new` that the agent is qualified for."""
    async with httpx.AsyncClient() as client:
        resp = await client.get(f"{GIG_API}/jobs?status=new&tags=ai-agent", headers=headers)
        resp.raise_for_status()
        return resp.json().get("data", [])

async def submit_result(job_id: str, output: Any) -> None:
    """Tell the platform the work is done; triggers escrow release."""
    payload = {"job_id": job_id, "result": json.dumps(output) if not isinstance(output, str) else output}
    async with httpx.AsyncClient() as client:
        resp = await client.post(f"{GIG_API}/jobs/{job_id}/complete", json=payload, headers=headers)
        resp.raise_for_status()

async def request_payout(job_id: str, amount_usdc: float) -> None:
    """Ask the platform to move escrow to your wallet (USDC on Base in this example)."""
    payload = {"job_id": job_id, "amount": amount_usdc, "currency": "USDC", "chain": "Base"}
    async with httpx.AsyncClient() as client:
        resp = await client.post(f"{GIG_API}/payouts", json=payload, headers=headers)
        resp.raise_for_status()
Enter fullscreen mode Exit fullscreen mode

Real‑world considerations

Issue What to watch for
Rate limits Platforms often cap calls per minute (e.g., 60 req/min). Implement a token‑bucket or use asyncio.Semaphore.
Idempotency Network glitches can duplicate a complete call. Pass an idempotency-key header (UUID) and store it locally to avoid double‑payout.
Escrow disputes If the client rejects the result, the platform may hold funds. Your agent should expose a revision endpoint that accepts feedback and reruns the chain.
Authentication renewal Tokens expire. Wrap the client in a refresh loop or rely on the platform’s SDK if available.
Legal Some platforms prohibit fully automated labor. Verify the TOS before you go live.

4. Payment Layer – x402 (HTTP 402 Payment Required)

Instead of waiting for the platform’s monthly payout, you can attach a micropayment directly to each request using the x402 spec. The client (the platform or a frontend) sends an HTTP 402 response with a Lightning/USDC invoice; the agent pays, then retries the original request with a Payment header.

Below is a minimal middleware that adds x402 handling to our agent’s HTTP caller.


python
# x402_client.py
import httpx, base64, os
from eth_account.messages import encode_defunct
from eth_account import Account

# Assume we hold a private key that controls a USDC address on Base
PRIVATE_KEY = os.getenv("AGENT_PRIVATE_KEY")
acct = Account.from_key(PRIVATE_KEY)

async def pay_if_needed(url: str, method: str = "GET", **kwargs) -> httpx.Response:
    async with httpx.AsyncClient() as client:
        resp = await client.request(method, url, **kwargs)
        if resp.status_code == 402:
            # Extract invoice from the `WWW-Authenticate: Payment` header
            auth = resp.headers.get("WWW-Authenticate", "")
            if not auth.startswith("Payment"):
                raise RuntimeError("Unknown 402 challenge")
            # Expected format: Payment invoice="<base64>", amount="0.05", currency="USDC"
            import re
            m = re.search(r'invoice="([^"]+)"', auth)
            if not m:
                raise RuntimeError("Malformed 402 header")
            invoice_b64 = m.group(1)
            invoice = base64.b64decode(invoice_b64).decode()
            # In a real implementation you'd verify the invoice, then send a USDC transfer.
            # Here we sign a dummy payload just to show the flow.
            message = encode_defunct(text=f"pay:{invoice}")
            signed = acct.sign_message(message)
            pay_headers = {
                "Payment": f"usdc:{signed.signature.hex()}",
                "Idempotency-Key": str(uuid.uuid4()),
            }
            # Retry the original request with payment proof
            resp = await client.request(method, url, headers=pay_headers, **kwargs)
        return resp
Enter fullscreen mode Exit fullscreen mode

Top comments (0)