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 building autonomous AI agents that need to earn money on gig marketplaces.


Introduction

Autonomous agents that can read a job posting, craft a proposal, negotiate terms, and collect payment are no longer pure science‑fiction. The moving parts are well‑known: a language model (LLM) for text generation/reasoning, APIs for the gig platform, and a settlement layer for micro‑transactions. What’s less discussed is how those pieces actually fit together in production, where latency, cost, and platform policy become hard constraints. This article walks through a minimal but functional pipeline, shows concrete code, and calls out the trade‑offs you’ll encounter when you try to turn a prompt into a paycheck.


1. High‑level architecture

+----------------+      +----------------+      +----------------+
|  Gig Platform  | <---> |  Agent Orchestrator | <---> |  Settlement (x402) |
|  (Upwork, Fiverr, …) |      (LLM chain + state)   |      (USDC on Base) |
+----------------+      +----------------+      +----------------+
Enter fullscreen mode Exit fullscreen mode
  • Agent Orchestrator – a lightweight service (e.g., a Cloudflare Worker or a small Flask app) that:
    1. Pulls new job postings via the platform’s public API or RSS feed.
    2. Feeds each posting into an LLM chain that extracts requirements, scores fit, and writes a proposal.
    3. Posts the proposal back to the platform.
    4. When a contract is awarded, triggers an x402 payment request for the agreed‑upon fee.

The chain itself can be broken into three deterministic steps: (a) parsing, (b) reasoning, (c) generation. Keeping them separate makes it easier to swap models, add guardrails, or cache intermediate results.


2. Choosing the LLM

For a production agent you need a model that is:

Property Why it matters Typical choice
Latency Proposals must be sent within seconds to beat human freelancers. Smaller instruct models (e.g., mistral-7b-instruct, phi-3-mini) deployed on a GPU‑enabled endpoint or via a provider with < 200 ms TTFT.
Cost per token You’ll be generating dozens of proposals per hour; token cost directly impacts profit. Open‑source models self‑hosted on spot instances, or a provider offering per‑million‑token pricing < $0.50.
Instruction following The chain relies on precise JSON output for parsing. Models fine‑tuned for instruction‑following (e.g., Nous‑Hermes‑2, Zephyr‑7b‑beta).
Safety/Policy Gig platforms forbid spammy or misleading proposals. Apply a lightweight classifier or regex filter after generation; do not rely solely on the model’s internal safety.

Avoid the temptation to reach for the largest GPT‑4‑class model “just in case”. The extra 2‑3× latency and token cost rarely translate into a higher win‑rate, especially when the proposal template is fairly rigid.


3. Working code snippets

Below is a minimal, functional orchestrator written in Python (FastAPI) that uses the LangChain abstraction for the LLM chain. Replace the model endpoint with your own inference service.

3.1 Dependencies

# pyproject.toml
[project]
name = "gig-agent"
dependencies = [
    "fastapi==0.110.0",
    "uvicorn[standard]==0.29.0",
    "langchain==0.2.5",
    "langchain-community==0.2.5",
    "httpx==0.27.0",
    "pydantic==2.7.0",
]
Enter fullscreen mode Exit fullscreen mode

3.2 LLM wrapper

# llm.py
from langchain_community.llms import VLLMOpenAI  # example using vLLM endpoint
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

LLM_ENDPOINT = "http://my-vllm:8000/v1"
LLM_MODEL = "mistral-7b-instruct"

llm = VLLMOpenAI(
    openai_api_base=LLM_ENDPOINT,
    model_name=LLM_MODEL,
    temperature=0.2,   # low temperature for deterministic proposals
    max_tokens=256,
)

# Prompt that forces JSON output
PROPOSAL_TEMPLATE = """
You are a freelance assistant. Given the job description below, output a valid JSON object with the fields:
- "title": a short, catchy title for your proposal (max 60 chars)
- "cover_letter": a concise cover letter (150-250 words) that addresses the client's needs,
  highlights relevant skills, and ends with a call‑to‑action.
Do not add any extra text outside the JSON.

Job description:
{job_desc}
"""
prompt = PromptTemplate(
    input_variables=["job_desc"],
    template=PROPOSAL_TEMPLATE,
)

proposal_chain = LLMChain(llm=llm, prompt=prompt)
Enter fullscreen mode Exit fullscreen mode

3.3 Fetching jobs (example: Upwork RSS)

# jobs.py
import httpx
import xml.etree.ElementTree as ET

UPWORK_RSS = "https://www.upwork.com/ab/feed/jobs/rss?q=python&sort=recency"

async def fetch_new_jobs(since: str) -> list[dict]:
    """
    Returns a list of dicts with keys: id, title, description, url.
    `since` is an ISO timestamp; we filter client‑side for simplicity.
    """
    async with httpx.AsyncClient(timeout=10.0) as client:
        r = await client.get(UPWORK_RSS)
        r.raise_for_status()
    root = ET.fromstring(r.text)
    jobs = []
    for item in root.findall("./channel/item"):
        job_id = item.findtext("guid")
        title = item.findtext("title")
        desc = item.findtext("description")
        link = item.findtext("link")
        pub = item.findtext("pubDate")
        # simple client‑side filter – replace with proper storage in prod
        jobs.append({"id": job_id, "title": title, "description": desc, "url": link, "pub": pub})
    return jobs
Enter fullscreen mode Exit fullscreen mode

3.4 Posting a proposal (Upwork API – simplified)

# platform.py
import httpx
from pydantic import BaseModel, Field

class Proposal(BaseModel):
    title: str = Field(max_length=60)
    cover_letter: str

UPWORK_API = "https://www.upwork.com/api/v1/jobs/{job_id}/proposals"
UPWORK_TOKEN = "YOUR_OAUTH_TOKEN"  # obtained via OAuth flow

async def submit_proposal(job_id: str, prop: Proposal) -> dict:
    headers = {"Authorization": f"Bearer {UPWORK_TOKEN}"}
    payload = prop.model_dump()
    async with httpx.AsyncClient() as client:
        r = await client.post(
            UPWORK_API.format(job_id=job_id),
            json=payload,
            headers=headers,
            timeout=15.0,
        )
        r.raise_for_status()
        return r.json()
Enter fullscreen mode Exit fullscreen mode

3.5 Orchestrator endpoint

# main.py
from fastapi import FastAPI, BackgroundTasks
from jobs import fetch_new_jobs
from llm import proposal_chain
from platform import submit_proposal
import datetime

app = FastAPI()

@app.post("/run-cycle")
async def run_cycle(background: BackgroundTasks):
    background.add_task(process_new_jobs)
    return {"status": "scheduled"}

async def process_new_jobs():
    now = datetime.datetime.now(datetime.timezone.utc).isoformat()
    jobs = await fetch_new_jobs(since=now)  # in production use a cursor or DB
    for job in jobs:
        try:
            # 1️⃣ generate proposal
            result = proposal_chain.run(job_desc=job["description"])
            # LangChain returns a string; we expect JSON
            import json
            prop_dict = json.loads(result)
            proposal = Proposal(**prop_dict)

            # 2️⃣ post to platform
            resp = await submit_proposal(job["id"], proposal)
            print(f"Posted proposal for {job['id']}: {resp}")

            # 3️⃣ (optional) listen for award → trigger x402 payment
            # … omitted for brevity …
        except Exception as e:
            print(f"Error processing {job['id']}: {e}")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Pulls the newest Upwork RSS items.
  • Feeds

Top comments (0)