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 earn money by completing microtasks on existing gig marketplaces.


1. Why Connect an LLM to a Gig Platform?

Most “agent‑as‑a‑service” demos stop at a chat UI. To turn that into revenue you need two things:

  1. A deterministic way to discover work – the platform must expose a list of tasks that can be programmatically fetched and filtered.
  2. A reliable execution loop – the agent must take the task description, produce an acceptable deliverable, and submit it back via the platform’s API (or web UI if no API exists).

If either piece is missing or flaky, the agent will either sit idle or generate work that gets rejected, wiping out any earnings. The trade‑off is therefore integration depth vs. development effort: using a platform’s official API gives you clear success/failure signals but often requires OAuth, rate‑limit handling, and compliance with their terms; scraping the UI avoids those hurdles but adds fragility and legal risk.

In practice, a hybrid approach works best: use the public API for task discovery and submission, and fall back to a lightweight HTML parser only for fields the API omits (e.g., attached files).


2. High‑Level Architecture

+-------------------+       +-------------------+       +-------------------+
|  Task Fetcher     | --->  |  LLM Chain Core   | --->  |  Result Submitter |
| (platform API)   |       | (prompt + tools) |       | (platform API)   |
+-------------------+       +-------------------+       +-------------------+
        ^                         ^                         ^
        |                         |                         |
        |   (polling / webhook)   |   (output validation)   |   (status callback)
        +-------------------------+-------------------------+
Enter fullscreen mode Exit fullscreen mode
  • Task Fetcher – a scheduler (cron, Cloudflare Workers trigger, or a simple while True: sleep) that calls the marketplace’s “available jobs” endpoint, applies filters (skill tags, price range, deadline), and pushes qualifying IDs onto a work queue (Redis, SQS, or even an in‑memory list for a prototype).
  • LLM Chain Core – built with LangChain or LlamaIndex; it receives a task payload, runs a series of prompts (analysis → plan → generate → review), and optionally calls external tools (code interpreter, image generator, web search).
  • Result Submitter – takes the agent’s output, formats it according to the platform’s spec (e.g., JSON for Upwork’s “submit work” endpoint, multipart form for Fiverr), and POSTs it. It also handles retries, exponential back‑off, and logging of submission IDs for later reconciliation.

Each component is deliberately decoupled so you can swap implementations (e.g., replace the LLM core with a fine‑tuned model hosted on your own GPU) without rewriting the whole pipeline.


3. Working Code Snippets

Below is a minimal, end‑to‑end example in Python that shows how to wire the three pieces together for a hypothetical gig platform called GigHub (the pattern maps 1:1 to Upwork, Fiverr, or Freelancer APIs after you replace the endpoint URLs and auth headers).

Note: The code is intentionally verbose about error handling to illustrate the trade‑offs; in production you would abstract retries into a library like tenacity and use structured logging.

# -------------------------------------------------
# 1. Task Fetcher – polling GigHub's public API
# -------------------------------------------------
import os, time, json, requests
from typing import List, Dict

API_BASE = os.getenv("GIGHUB_API", "https://api.gighub.com/v1")
API_KEY  = os.getenv("GIGHUB_KEY")          # bearer token from developer portal

def fetch_open_tasks() -> List[Dict]:
    """Return a list of task dicts that match our skill & price filters."""
    headers = {"Authorization": f"Bearer {API_KEY}"}
    params  = {"skill": "python", "max_price": 5.00, "status": "open"}
    resp = requests.get(f"{API_BASE}/tasks", headers=headers, params=params, timeout=10)
    resp.raise_for_status()
    data = resp.json()
    # GigHub returns { "tasks": [{id, title, description, budget, ...}] }
    return data.get("tasks", [])

# -------------------------------------------------
# 2. LLM Chain Core – using LangChain + OpenAI
# -------------------------------------------------
from langchain import OpenAI, LLMChain, PromptTemplate
from langchain.tools import Tool
from langchain.agents import initialize_agent, AgentType

llm = OpenAI(temperature=0.2, model_name="gpt-4o-mini")  # cheap, decent quality

# Simple tool that pretends to run a Python snippet and returns stdout.
def python_tool(code: str) -> str:
    import subprocess, sys, textwrap
    try:
        result = subprocess.run(
            [sys.executable, "-c", code],
            capture_output=True,
            text=True,
            timeout=5,
        )
        return result.stdout or result.stderr
    except Exception as e:
        return str(e)

tools = [
    Tool(
        name="PythonRunner",
        func=python_tool,
        description="Executes a short Python snippet and returns its output.",
    )
]

# Prompt template that forces the agent to think step‑by‑step.
template = """You are a freelance developer tasked with completing the following gig:

{task_description}

Your goal is to produce a correct solution that satisfies the requirements.
You may use the PythonRunner tool to test snippets.
When you are ready, output the final answer in the format:
Enter fullscreen mode Exit fullscreen mode
Do not include any extra commentary outside the fenced block.
"""
prompt = PromptTemplate(input_variables=["task_description"], template=template)

llm_chain = LLMChain(llm=llm, prompt=prompt)

# Initialize a zero‑shot agent that can decide when to call the tool.
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=False,
    handle_parsing_errors=True,
)

def solve_task(task: Dict) -> str:
    """Run the agent on a single task and return the raw text output."""
    description = task["description"]
    # The agent expects a string; we feed the full prompt template via the chain.
    # We could also call agent.run directly, but using the chain lets us inspect
    # intermediate thoughts if needed.
    result = llm_chain.run(task_description=description)
    # The chain returns the raw LLM output; we still run the agent to allow tool use.
    final = agent.run(result)
    return final.strip()

# -------------------------------------------------
# 3. Result Submitter – POST back to GigHub
# -------------------------------------------------
def submit_result(task_id: str, payload: str) -> bool:
    """Submit the solution. Returns True on HTTP 2xx."""
    headers = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
    body = {"task_id": task_id, "solution": payload}
    resp = requests.post(f"{API_BASE}/tasks/{task_id}/submit",
                         json=body,
                         headers=headers,
                         timeout=10)
    if 200 <= resp.status_code < 300:
        return True
    else:
        # Log the error; in a real system you would push to a dead‑letter queue.
        print(f"Submit failed {resp.status_code}: {resp.text}")
        return False

# -------------------------------------------------
# 4. Main loop – glue everything together
# -------------------------------------------------
def worker_loop(poll_interval: int = 30):
    while True:
        try:
            tasks = fetch_open_tasks()
            for t in tasks:
                # Avoid re‑processing the same task if you persist state elsewhere.
                print(f"Processing task {t['id']}: {t['title'][:40]}...")
                answer = solve_task(t)
                ok = submit_result(t["id"], answer)
                print(f"  → Submission {'OK' if ok else 'FAILED'}")
        except Exception as e:
            print(f"Loop error: {e}")
        time.sleep(poll_interval)

if __name__ == "__main__":
    worker_loop()
Enter fullscreen mode Exit fullscreen mode

What the snippet shows

Step What you get Trade‑off
Task Fetcher Simple HTTP GET with filtering. No webhook support → polling latency (up to poll_interval seconds).
LLM Chain Core Uses a mature library (LangChain) to separate prompting from tool use. Adds dependency weight; debugging agent reasoning can be opaque.
Result Submitter Clear success/failure based on HTTP status. Requires you to know the exact submission schema; some platforms need multipart/file uploads which the snippet omits.
Loop Demonstrates a resilient while True with error logging. In production you’d replace print with structured logs, add metrics, and possibly run the loop as a containerized job.

You can replace the OpenAI LLM with a locally hosted model (e.g., llama.cpp via an HTTP endpoint) by changing the llm instantiation; the rest of the pipeline stays identical.


4. Honest Trade‑offs & Practical Tips

Area Real‑world cost Mitigation
Latency Each loop iteration does: API call → LLM inference (≈ 0.8‑2 s for gpt‑4o‑mini) → submit. If you set poll_interval to 10 s, you could lose ~10 s of potential earnings per task. Use webhooks if the platform offers them (e.g., Upwork’s “job‑posted

Top comments (0)