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

Published on dev.to – a practical guide for developers building autonomous AI agents


Introduction

When you talk about “LLM agents” the conversation often jumps straight to futuristic demos. In reality, most useful agents are just a prompt, a bit of logic, and an API call to a service that already pays people for work. This article shows how to stitch a simple LangChain‑based LLM chain to a real‑world gig platform (we’ll use the Upwork REST API as an example) and route the earned revenue through the x402 micropayment protocol. The goal is a working skeleton you can adapt, not a sales pitch.


The Problem: From Text to Dollars

A developer wants an agent that:

  1. Reads a natural‑language request (“find me a short‑term Python scraping job paying at least $20”).
  2. Queries a gig marketplace for matching listings.
  3. Generates a proposal, submits it, and, if accepted, triggers a payment flow.

Doing this manually is brittle: you need to handle authentication, rate limits, pagination, and the inevitable mismatches between what the model says and what the API expects. By isolating each concern into a reusable component, you can test and replace parts without rewriting the whole agent.


Architecture Overview

+----------------+      +----------------+      +----------------+
|  Prompt Engine | ---> |  Gig Adapter   | ---> |  x402 Settler  |
+----------------+      +----------------+      +----------------+
        ^                         ^                         ^
        |                         |                         |
   User Input               Platform API                Wallet/USDC
Enter fullscreen mode Exit fullscreen mode
  • Prompt Engine – turns free‑form text into structured parameters (job filters, proposal tone).
  • Gig Adapter – translates those parameters into HTTP calls to the platform, normalises responses, and handles retries.
  • x402 Settler – watches for a successful contract award, creates an x402 invoice, and settles it in USDC on Base.

Each block is a thin wrapper around an existing library, making the agent easy to audit and to swap out (e.g., replace Upwork with Fiverr or a internal job board).


Prompt Engine – Structuring the Request

We use LangChain’s LLMChain with a simple Jinja2 template. The model only needs to output JSON; we enforce it with a Pydantic parser so downstream code never has to guess fields.

from langchain import LLMChain, PromptTemplate
from langchain.chat_models import ChatOpenAI
from pydantic import BaseModel, Field
from typing import Optional

class JobSpec(BaseModel):
    keywords: list[str] = Field(..., description="Skills to match")
    min_price: Optional[float] = Field(None, description="Minimum hourly rate in USD")
    max_price: Optional[float] = Field(None, description="Maximum hourly rate in USD")
    duration: Optional[str] = Field(None, description="e.g., '1‑week', 'ongoing'")

prompt = PromptTemplate(
    input_variables=["user_request"],
    template=(
        "You are a job‑search assistant. Extract the following fields from the user request:\n"
        "- keywords (list of strings)\n"
        "- min_price (float or null)\n"
        "- max_price (float or null)\n"
        "- duration (string or null)\n"
        "Return ONLY a JSON object matching the schema.\n\n"
        "User request: {user_request}\n"
    ),
)

llm = ChatOpenAI(temperature=0, model_name="gpt-4o-mini")
chain = LLMChain(llm=llm, prompt=prompt)

def extract_spec(user_request: str) -> JobSpec:
    raw = chain.run(user_request=user_request)
    # LangChain returns a string; we parse with Pydantic for validation
    return JobSpec.parse_raw(raw)
Enter fullscreen mode Exit fullscreen mode

Trade‑off: Using a powerful model (gpt-4o-mini) improves extraction accuracy but adds latency (~300‑500 ms) and cost (~$0.0008 per call). For high‑volume agents you could replace the LLM with a regex‑based parser or a fine‑tuned smaller model, accepting a higher false‑negative rate.


Gig Adapter – Talking to Upwork

Upwork’s API requires OAuth 2.0 bearer tokens and uses cursor‑based pagination. The adapter below hides those details, returns a list of normalized job dicts, and implements exponential back‑off for 429 responses.

import requests
import time
from typing import List, Dict

UPWORK_BASE = "https://www.upwork.com/api/v1"
TOKEN = "YOUR_UPWORK_OAUTH_TOKEN"  # store securely, e.g., in Vault or env var

def _get_with_retry(url: str, params: Dict) -> Dict:
    for attempt in range(5):
        resp = requests.get(url, headers={"Authorization": f"Bearer {TOKEN}"}, params=params)
        if resp.status_code == 429:
            wait = 2 ** attempt
            time.sleep(wait)
            continue
        resp.raise_for_status()
        return resp.json()
    raise RuntimeError("Exceeded retry limit")

def search_jobs(spec: JobSpec) -> List[Dict]:
    params = {
        "q": " ".join(spec.keywords),
        "page": 1,
        "page_size": 20,
    }
    if spec.min_price is not None:
        params["min_rate"] = spec.min_price
    if spec.max_price is not None:
        params["max_rate"] = spec.max_price
    if spec.duration:
        params["duration"] = spec.duration

    data = _get_with_retry(f"{UPWORK_BASE}/jobs/search", params)
    jobs = []
    for raw in data.get("jobs", []):
        jobs.append({
            "id": raw["uid"],
            "title": raw["title"],
            "url": raw["ref"],
            "budget": raw.get("budget"),
            "skills": raw.get("skills", []),
        })
    return jobs
Enter fullscreen mode Exit fullscreen mode

Trade‑off: The adapter is synchronous for simplicity; in production you’d wrap it in an async client or a task queue (Celery, Dramatiq) to avoid blocking the LLM chain. Error handling is deliberately minimal—real code should differentiate between auth errors, transient network issues, and platform‑specific rejections (e.g., job already filled).


x402 Settler – Getting Paid

x402 is a HTTP‑based micropayment protocol that lets you attach an invoice to a 402 Response. When the client pays, the server receives USDC on Base. For our agent we only need to create an invoice after a contract is awarded; the client (the gig platform) will pay it when releasing funds.

from x402 import Invoice, PaymentResolver
from eth_account import Account
import os

# Private key controlling the USDC receiving address (keep off‑repo!)
PRIVATE_KEY = os.getenv("AGENT_WALLET_KEY")
resolver = PaymentResolver(Account.from_key(PRIVATE_KEY))

def create_invoice(job_id: str, amount_usdc: int) -> str:
    """
    amount_usdc is the micro‑units of USDC (6 decimals).
    Returns a payment URL the client can follow.
    """
    inv = Invoice(
        payer="upwork_client",   # placeholder; in practice you’d fill with the client’s address
        payee=resolver.address,
        amount=amount_usdc,
        asset="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",  # USDC on Base
        description=f"Payment for Upwork job {job_id}",
    )
    return inv.to_url()  # e.g., https://payment.x402.org/?invoice=...
Enter fullscreen mode Exit fullscreen mode

When the agent detects a job status change to “awarded”, it calls create_invoice with the agreed‑upon amount (converted to USDC via a price oracle if needed) and stores the URL. The platform’s payment flow can then be triggered manually or via a webhook that redirects the client to the invoice URL.

Trade‑off: x402 works only on chains that support the protocol (currently Base, Polygon, and a few testnets). If your gig platform pays in fiat or another crypto, you’ll need a bridge or a separate settlement

Top comments (0)