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 autonomous agents that can actually earn money requires more than a clever prompt. You need reliable plumbing between the language model, the gig‑platform APIs, and a payment settlement layer. Below is a walk‑through of the pieces that work today, the code that glues them together, and the trade‑offs you’ll hit when you move from demo to production.


1. Why a Chain, Not Just a Prompt?

A single LLM call can draft a proposal, but gig platforms expect a sequence of actions:

  1. Discover a task that matches the agent’s skill set.
  2. Parse the task description to extract requirements (budget, deadline, tech stack).
  3. Generate a tailored bid or solution artifact.
  4. Submit the bid via the platform’s API.
  5. Handle the platform’s response (acceptance, rejection, request for clarification).

Each step can be modeled as a tool in an LLM chain. The chain gives you deterministic error handling, retries, and the ability to swap models without rewriting platform‑specific logic.


2. High‑Level Architecture

+----------------+      +----------------+      +----------------+
|   Scheduler    | ---> |   LLM Chain    | ---> | Gig‑Platform   |
| (cron / trigger)|     | (prompt + tools)|     |  API (REST/GraphQL)|
+----------------+      +----------------+      +----------------+
          ^                        |                         |
          |                        v                         v
    +----------------+      +----------------+      +----------------+
    |  State Store   | <---> |  Payment Ledger| <---> |  Escrow / USDC |
    +----------------+      +----------------+      +----------------+
Enter fullscreen mode Exit fullscreen mode
  • Scheduler – a lightweight job (e.g., a Cloudflare Worker cron) that kicks off the chain every few minutes.
  • LLM Chain – built with LangChain‑like primitives; each step is a Python function that returns a structured payload.
  • Gig‑Platform API – we’ll show Upwork’s GraphQL endpoint as an example; the pattern applies to Fiverr, Freelancer, etc.
  • State Store – Redis or SQLite to keep track of which tasks have been seen, bids sent, and outcomes.
  • Payment Ledger – records the USDC amount earned per successful bid; the x402 spec lets you attach a micro‑payment to each HTTP response.

3. Prompt Engineering & Tool Definitions

We keep prompts short, version‑controlled, and testable. Below is a reusable template for the “Parse Task” step.

# prompts/parse_task.txt
You are a careful analyst. Given the following gig description, extract:
- title (string)
- required_skills (list of strings)
- budget_min (float, USD)
- budget_max (float, USD)
- deadline (ISO8601 string or null)
Return ONLY a JSON object with those keys. If a field cannot be determined, set it to null.

---  
{{task_description}}
Enter fullscreen mode Exit fullscreen mode

The corresponding tool function:

import json
import re
from typing import Any, Dict, Optional

def parse_task_tool(task_description: str) -> Dict[str, Any]:
    """
    Sends the description to the LLM and forces a JSON output.
    Falls back to a regex‑based heuristic if the model returns malformed JSON.
    """
    prompt = open("prompts/parse_task.txt").read().replace("{{task_description}}", task_description)
    raw = call_llm(prompt, temperature=0.0, max_tokens=200)   # see §4 for call_llm
    # Attempt to isolate JSON
    json_match = re.search(r"\{.*\}", raw, re.DOTALL)
    if not json_match:
        raise ValueError("LLM did not return JSON-like output")
    try:
        data = json.loads(json_match.group(0))
    except json.JSONDecodeError:
        # Very lightweight fallback: look for key=value patterns
        data = {}
        for key in ["title", "required_skills", "budget_min", "budget_max", "deadline"]:
            m = re.search(rf"{key}\s*[:=]\s*['\"]?([^'\"\n,]+)", raw, re.I)
            if m:
                val = m.group(1).strip()
                if key == "required_skills":
                    data[key] = [s.strip() for s in val.split(",")]
                elif key in ("budget_min", "budget_max"):
                    try:
                        data[key] = float(val)
                    except ValueError:
                        data[key] = None
                elif key == "deadline":
                    data[key] = val if val.lower() != "null" else None
                else:
                    data[key] = val
            else:
                data[key] = None
    # Normalise types
    data.setdefault("required_skills", [])
    data.setdefault("budget_min", None)
    data.setdefault("budget_max", None)
    data.setdefault("deadline", None)
    return data
Enter fullscreen mode Exit fullscreen mode

Trade‑off: Using a low‑temperature model improves JSON reliability but can make the output brittle if the prompt drifts. Keeping a fallback parser prevents total failure at the cost of slightly less nuanced extraction.


4. Calling the LLM – Minimal Wrapper

You can swap the provider without touching the rest of the chain.

import os
import requests

def call_llm(prompt: str, temperature: float = 0.2, max_tokens: int = 500) -> str:
    """
    Simple wrapper around OpenAI's chat completions API.
    Replace the endpoint and headers for other providers (e.g., Anthropic, local vLLM).
    """
    api_key = os.getenv("OPENAI_API_KEY")
    if not api_key:
        raise RuntimeError("OPENAI_API_KEY not set")
    url = "https://api.openai.com/v1/chat/completions"
    payload = {
        "model": "gpt-4o-mini",          # cheap, decent reasoning
        "messages": [{"role": "user", "content": prompt}],
        "temperature": temperature,
        "max_tokens": max_tokens,
    }
    headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
    resp = requests.post(url, json=payload, headers=headers, timeout=15)
    resp.raise_for_status()
    data = resp.json()
    return data["choices"][0]["message"]["content"].strip()
Enter fullscreen mode Exit fullscreen mode

Trade‑off: gpt-4o-mini costs roughly $0.00015 per 1k tokens, which keeps per‑call spend under a cent for most prompts. If latency is critical, a local quantized model (e.g., Mistral‑7B‑instruct) can cut network latency but adds maintenance overhead and often lower reasoning quality.


5. Interacting with a Gig Platform (Upwork Example)

Upwork’s public GraphQL endpoint requires an OAuth2 token. The snippet below shows how to search for new jobs, generate a bid, and submit it.


python
import time
import uuid
from datetime import datetime, timezone

UPWORK_ENDPOINT = "https://www.upwork.com/api/graphql"
UPWORK_TOKEN = os.getenv("UPWORK_ACCESS_TOKEN")  # short‑lived, refresh via OAuth flow

def graphql_query(query: str, variables: Dict[str, Any] = None) -> Dict[str, Any]:
    headers = {
        "Authorization": f"Bearer {UPWORK_TOKEN}",
        "Content-Type": "application/json",
    }
    payload = {"query": query, "variables": variables or {}}
    r = requests.post(UPWORK_ENDPOINT, json=payload, headers=headers, timeout=10)
    r.raise_for_status()
    return r.json()

def find_recent_jobs(skill: str, limit: int = 5) -> list:
    """
    Returns a list of job dicts posted in the last hour.
    """
    query = """
    query RecentJobs($skill: String!, $limit: Int!) {
      jobs(query: $skill, first: $limit, sortBy: POST_DATE) {
        nodes {
          id
          title
          description
          budget {
            amount
            currency
          }
          deadline
        }
      }
    }
    """
    data = graphql_query(query, {"skill": skill, "limit": limit})
    return [
        {
            "id": n["id"],
            "title": n["title"],
            "description": n["description"],
            "budget_min": float(n["budget"]["amount"]) if n["budget"] else None,
            "budget_max": float(n["budget"]["amount"]) if n["budget"] else None,
            "deadline": n["deadline"],
        }
        for n in data["data"]["jobs"]["nodes"]
    ]

def create_bid(job_id: str, cover_letter: str, amount_usd: float) -> str:
    """
    Submits a proposal and returns the Upwork proposal ID.
    """
    mutation = """
    mutation SubmitProposal($input: SubmitProposalInput!) {
      submitProposal(input: $input) {
        proposalId
      }
    }
    """
    variables = {
        "input": {
            "jobId": job_id,
            "coverLetter": cover_letter,
            "amount": amount_usd,
            "currency": "USD",
        }
    }
    resp = graphql_query(mutation, variables)
    return resp["data"]["submitProposal"]["pro
Enter fullscreen mode Exit fullscreen mode

Top comments (0)