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

Target audience: developers who are building autonomous AI agents that need to accept work, perform it, and get paid on existing gig marketplaces.


1. Why Bother Chaining an LLM to a Gig Platform?

Gig platforms already expose APIs for posting jobs, submitting deliverables, and handling payments. If an autonomous agent can:

  1. Consume a natural‑language request (the “prompt”) from a client or from a job posting,
  2. Reason about the required steps using an LLM‑driven chain,
  3. Execute those steps (code generation, data scraping, simple design, etc.), and
  4. Report completion back to the platform and receive payment in a programmable token (USDC on Base via the x402 standard),

then the agent becomes a service that can be discovered, invoked, and settled without human intervention.

The trade‑off is obvious: you replace a deterministic, often‑scripted integration with a stochastic component that can hallucinate, drift, or exceed cost budgets. The rest of this article walks through a minimal, production‑ish implementation that makes those trade‑offs explicit.


2. High‑Level Architecture

+----------------+          +----------------+          +----------------+
|  Gig Platform  | <--API-->|  Agent Service | <--x402-->|  Payment Lead  |
| (Upwork/Fiverr) |          | (FastAPI +    |          |  (Base USDC)   |
+----------------+          |  LLM Chain)   |          +----------------+
                            +----------------+
Enter fullscreen mode Exit fullscreen mode
  1. Gig Platform – the source of jobs (webhook or polling) and the sink for deliverables.
  2. Agent Service – a thin HTTP wrapper that validates incoming jobs, runs the LLM chain, and returns an artifact.
  3. LLM Chain – a sequence of prompts, tool calls, and validation steps (built with LangChain‑like primitives or a custom loop).
  4. Payment Lead – the x402 middleware that attaches a USDC invoice to each request and releases funds on successful response.

The service is deliberately stateless; each request carries enough context (job ID, prompt, required output format) to be processed independently.


3. The LLM Chain – Core Logic

Below is a self‑contained Python snippet that shows a three‑step chain:

  1. Understand – rephrase the user request into a concrete task specification.
  2. Execute – call a deterministic tool (here, a Python code executor) to produce the artifact.
  3. Validate – run a simple sanity check (type, length, or regex) before returning the result.
# agent/chain.py
from __future__ import annotations
import json, textwrap, subprocess, sys
from typing import Any, Dict

# ---- 1. Prompt templating -------------------------------------------------
UNDERSTAND_TMPL = textwrap.dedent("""
    You are a helpful assistant that turns a vague request into a precise,
    executable specification. Return ONLY a JSON object with the keys:
    - "language": programming language (e.g., "python")
    - "code": a string containing the full source code to run
    - "inputs": dict of any required stdin values (can be empty)
    Request: {user_prompt}
""")

# ---- 2. Tool: Python sandbox ------------------------------------------------
def run_python(code: str, stdin: str = "") -> Dict[str, Any]:
    """
    Executes the supplied code in an isolated subprocess.
    Returns { "stdout": str, "stderr": str, "returncode": int }.
    """
    proc = subprocess.run(
        [sys.executable, "-c", code],
        input=stdin.encode(),
        capture_output=True,
        timeout=12,          # hard limit to avoid runaway loops
    )
    return {
        "stdout": proc.stdout.decode(),
        "stderr": proc.stderr.decode(),
        "returncode": proc.returncode,
    }

# ---- 3. Validation ---------------------------------------------------------
def validate_output(spec: Dict[str, Any], result: Dict[str, Any]) -> bool:
    """
    Example validation: we expect the program to print a single line
    that matches ^\\d+(\\.\\d+)?$ (a number). Adjust to your domain.
    """
    out = result.get("stdout", "").strip()
    if not out:
        return False
    import re
    return bool(re.match(r"^\d+(\.\d+)?$", out))

# ---- Orchestrator -----------------------------------------------------------
def process_prompt(user_prompt: str) -> Dict[str, Any]:
    # Step 1: ask the LLM to produce a spec (here we mock the call)
    # In production you would call OpenAI, Anthropic, or a self‑hosted model.
    spec_json = _call_llm(UNDERSTAND_TMPL.format(user_prompt=user_prompt))
    try:
        spec = json.loads(spec_json)
    except json.JSONDecodeError as e:
        raise ValueError(f"LLM did not return valid JSON: {e}")

    # Step 2: execute the generated code
    exec_result = run_python(spec.get("code", ""), stdin=json.dumps(spec.get("inputs", {})))

    # Step 3: validate
    if not validate_output(spec, exec_result):
        raise RuntimeError("Validation failed – output does not meet spec")

    return {
        "spec": spec,
        "execution": exec_result,
    }

# ---------------------------------------------------------------------------
def _call_llm(prompt: str) -> str:
    """
    Placeholder for the actual LLM call.
    Replace with your provider's SDK; keep temperature low (0.0–0.2) for determinism.
    """
    # Example using OpenAI's chat completion (you would inject API key via env)
    from openai import OpenAI
    client = OpenAI()
    resp = client.chat.completions.create(
        model="gpt-4o-mini",          # cheap, fast enough for most gig tasks
        messages=[{"role": "user", "content": prompt}],
        temperature=0.1,
        max_tokens=800,
    )
    return resp.choices[0].message.content.strip()
Enter fullscreen mode Exit fullscreen mode

What this snippet shows

  • Deterministic sandbox – the code execution step is isolated and time‑boxed, limiting the blast radius of a hallucinated script.
  • Low temperature – we keep the LLM’s creativity in check; the chain relies on the model mainly for translation from natural language to a structured spec, not for open‑ended generation.
  • Validation gate – a simple regex (or any domain‑specific check) prevents returning malformed work to the gig platform.

If any step fails, the service returns an HTTP 4xx/5xx with an error payload; the x402 middleware will not settle the invoice, protecting both parties.


4. Wiring the Chain Into a Gig Platform

Most platforms expose a webhook for “new job posted”. For illustration we’ll use a generic JSON payload:

{
  "job_id": "gj_123abc",
  "title": "Calculate the factorial of 7",
  "description": "Return the result as a plain integer.",
  "budget": 0.05   // USDC
}
Enter fullscreen mode Exit fullscreen mode

4.1 FastAPI endpoint

# agent/app.py
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
from .chain import process_prompt

app = FastAPI(title="LLM‑Gig Agent")

class GigJob(BaseModel):
    job_id: str
    title: str
    description: str
    budget: float   # USDC amount expected by the caller

@app.post("/handle_job")
async def handle_job(payload: GigJob, request: Request):
    # 1️⃣ Build the prompt the LLM will see
    user_prompt = f"""Task: {payload.title}
    Details: {payload.description}
    Return only the numeric answer."""

    try:
        outcome = process_prompt(user_prompt)
    except Exception as exc:
        # Log for observability; do not leak internals to the caller
        raise HTTPException(status_code=400, detail=str(exc))

    # 2️⃣ Prepare the deliverable – here we just echo the stdout
    deliverable = outcome["execution"]["stdout"].strip()

    # 3️⃣ Respond with a structure the platform expects
    return {
        "job_id": payload.job_id,
        "status": "completed",
        "result": deliverable,
    }
Enter fullscreen mode Exit fullscreen mode

The endpoint is deliberately tiny: it receives a job, builds a prompt, runs the chain, and returns the result. All heavy lifting stays inside process_prompt.

4.2 Adding x402 Payment Metadata

The x402 spec defines a HTTP header X-402-Payment-Required that contains a JSON‑encoded invoice. A minimal middleware (or a sidecar like Cloudflare Workers) can attach it:


python
# agent/middleware.py
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
import json

class X402Middleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response: Response = await call_next(request)
        # If we succeeded, attach the invoice for the *next* request (client‑side)
        # In practice the client reads the header before sending money.
        if 200 <= response.status_code < 300:
            invoice = {
                "payload": {
                    "destination": "0xYourAgentWallet",   // USDC on Base
                    "amount": "0.05",                     // matches job.budget
                    "currency": "USDC",
                    "chain": "base"
                },
                # optional: expiration, metadata, etc.
            }
            response.headers["X-402-Payment-Required"] = json.dumps(invoice)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)