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 earn money isn’t science‑fiction—it’s a matter of connecting a language model to the APIs that power freelance marketplaces. This post walks through a minimal, production‑style pipeline: a prompt‑driven LLM chain, a thin wrapper that turns model output into concrete platform actions, and the practical concerns you’ll hit when you try to run it at scale.


1. The Core Idea

A gig platform (e.g., a job board, a micro‑task site, or a custom SaaS marketplace) typically exposes:

Action Typical endpoint What you need to send What you get back
List open tasks GET /tasks?status=open Auth token, optional filters JSON array of task objects
Claim a task POST /tasks/{id}/claim Auth token, worker ID Confirmation or error
Submit work POST /tasks/{id}/submit Auth token, result payload Payment status, reviewer feedback
Get payout GET /payouts?worker={id} Auth token Balance, pending transactions

If you can replace the human decision‑maker in the loop with an LLM that (1) reads a task description, (2) decides whether it’s worth doing, (3) produces the required artifact, and (4) posts the result, you have an autonomous earning agent. The chain looks like:

Prompt → LLM → (optional) Tool use → Action API → Feedback → (loop)
Enter fullscreen mode Exit fullscreen mode

The rest of this article shows a concrete implementation in Python, using the lightweight [LangChain Expression Language (LCEL)] for the prompt‑model part and httpx for async HTTP calls to the platform.


2. Prerequisites

  • Python 3.11+
  • An LLM endpoint that supports chat completions (OpenAI‑compatible, Anthropic, or a self‑hosted model served via TGI/vLLM).
  • Credentials for the target gig platform (usually a bearer token or API key).
  • A small amount of USDC on Base for the x402 payment demo (see the final note).

Install the core deps:

pip install langchain langchain-core httpx[http2] python-dotenv
Enter fullscreen mode Exit fullscreen mode

3. Prompt Engineering – From Raw Description to Structured Plan

The model does not need to know the internals of the platform; it only needs to output a plan that our wrapper can execute. A simple JSON schema works well:

{
  "task_id": "string",
  "action": "claim | submit | skip",
  "payload": { /* depends on action */ },
  "confidence": 0.0-1.0
}
Enter fullscreen mode Exit fullscreen mode

We ask the model to fill this schema using a few‑shot prompt. Below is the prompt template (store it in prompt.txt):

You are an autonomous worker for a gig platform.  
Given the following task description, decide whether to claim it, skip it, or (if already claimed) submit a result.  
Respond ONLY with a JSON object that matches the schema:

{
  "task_id": "<string>",
  "action": "claim | submit | skip",
  "payload": { /* see notes */ },
  "confidence": <float between 0 and 1>
}

Notes:
- If action is "claim", payload can be empty.
- If action is "submit", payload must contain a "result" field with the work product (e.g., generated text, image URL, code snippet).
- If action is "skip", payload can be empty.
- Set confidence to reflect how sure you are about the decision.

Examples:
Task: "Write a 150‑word blog intro about renewable energy."
{
  "task_id": "t123",
  "action": "submit",
  "payload": { "result": "Renewable energy…" },
  "confidence": 0.94
}
Task: "Design a logo for a coffee shop."
{
  "task_id": "t456",
  "action": "skip",
  "payload": {},
  "confidence": 0.78
}
Enter fullscreen mode Exit fullscreen mode

4. Wiring the LLM Chain

We’ll use LangChain’s ChatPromptTemplate + a chat model + a JsonOutputParser to enforce the schema. The code below assumes an OpenAI‑compatible endpoint; swap the base_url and api_key for your provider.

# agent.py
import os
import json
import httpx
from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser
from langchain_openai import ChatOpenAI   # replace with your chat model class

load_dotenv()  # loads OPENAI_API_KEY, PLATFORM_TOKEN, PLATFORM_BASE_URL

# ----------------------------------------------------------------------
# 1️⃣ Prompt → Model → Structured output
# ----------------------------------------------------------------------
prompt = ChatPromptTemplate.from_template(open("prompt.txt").read())
parser = JsonOutputParser()  # validates JSON shape & types

model = ChatOpenAI(
    model="gpt-4o-mini",          # or any model you have access to
    temperature=0.2,
    max_tokens=500,
    api_key=os.getenv("OPENAI_API_KEY"),
)

chain = prompt | model | parser   # LCEL composition

# ----------------------------------------------------------------------
# 2️⃣ Platform client (async for scalability)
# ----------------------------------------------------------------------
PLATFORM_BASE = os.getenv("PLATFORM_BASE_URL")  # e.g. https://api.gigexample.com
PLATFORM_TOKEN = os.getenv("PLATFORM_TOKEN")

async def platform_request(method: str, path: str, json_data=None):
    headers = {"Authorization": f"Bearer {PLATFORM_TOKEN}"}
    async with httpx.AsyncClient(base_url=PLATFORM_BASE, headers=headers, timeout=30.0) as client:
        resp = await client.request(method, path, json=json_data)
        resp.raise_for_status()
        return resp.json()

# ----------------------------------------------------------------------
# 3️⃣ Core loop: fetch tasks, run chain, act
# ----------------------------------------------------------------------
async def process_one_task(task: dict):
    task_id = task["id"]
    description = task["description"]

    # Ask the LLM what to do
    try:
        decision = await chain.ainvoke({"task_description": description})
    except Exception as e:
        print(f"[{task_id}] LLM error: {e}")
        return

    # Basic sanity check
    if not isinstance(decision, dict) or "action" not in decision:
        print(f"[{task_id}] Invalid decision: {decision}")
        return

    action = decision["action"]
    payload = decision.get("payload", {})
    confidence = decision.get("confidence", 0.0)

    print(f"[{task_id}] LLM suggests {action} (conf={confidence:.2f})")

    # ------------------------------------------------------------------
    # Execute the chosen action against the platform
    # ------------------------------------------------------------------
    try:
        if action == "claim":
            await platform_request("POST", f"/tasks/{task_id}/claim")
            print(f"[{task_id}] Claimed")
        elif action == "submit":
            # The payload must contain a "result" field per our schema
            result = payload.get("result")
            if result is None:
                raise ValueError("Submit action missing 'result'")
            await platform_request(
                "POST",
                f"/tasks/{task_id}/submit",
                {"result": result}
            )
            print(f"[{task_id}] Submitted")
        elif action == "skip":
            print(f"[{task_id}] Skipped")
        else:
            print(f"[{task_id}] Unknown action: {action}")
    except httpx.HTTPStatusError as exc:
        print(f"[{task_id}] Platform error {exc.response.status_code}: {exc.response.text}")

async def worker_loop(poll_interval: int = 10):
    while True:
        try:
            open_tasks = await platform_request("GET", "/tasks?status=open")
            for task in open_tasks:
                await process_one_task(task)
        except Exception as e:
            print(f"Loop error: {e}")
        await httpx.AsyncClient().sleep(poll_interval)

if __name__ == "__main__":
    import asyncio
    asyncio.run(worker_loop())
Enter fullscreen mode Exit fullscreen mode

What this script does

  1. Fetches all open tasks from the platform (you can add pagination or filters).
  2. Sends each task’s description to the LLM via the prompt‑chain, receiving a structured decision.
  3. Executes the decided action (claim, submit, or skip) by calling the appropriate platform endpoint.
  4. Loops forever, pausing a configurable interval between polls.

5. Honest Trade‑offs

Aspect Benefit Cost / Risk
Latency The LLM call dominates (≈300‑800 ms for a small model). Adding network round‑trips to the platform adds another 100‑200 ms. Real‑time bidding wars (e.g., “first to claim wins”) may be lost if your poll interval is too high. Reducing interval increases platform load and may get you rate‑limited.
Model quality A 7B‑parameter open‑model can produce decent JSON for simple tasks; larger models improve correctness but raise cost. Hallucinations can lead to invalid JSON or malformed payloads, causing rejected submissions and possible reputation penalties. Validation (the JsonOutputParser) catches structural errors but not semantic ones (e.g., submitting unrelated text).
Funds handling Earnings accrue in the platform

Top comments (0)