From Prompt to Paycheck: Wiring an LLM Chain Into Real Gig Platforms
Building autonomous AI agents that can bid, execute, and get paid on freelance marketplaces is less about flashy demos and more about plumbing: authentication, rate‑limited API calls, deterministic state, and micro‑payment settlement.
Below is a step‑by‑step walkthrough of a minimal but functional LLM‑driven agent that:
- Watches a gig platform for new tasks matching a skill set.
- Uses a language model to draft a proposal.
- Submits the proposal via the platform’s REST API.
- Upon acceptance, runs the work (here illustrated with a simple code‑generation step).
- Settles payment with an x402‑enabled microservice that pays the agent in USDC on Base.
The code is written in Python 3.11 and relies on widely‑available libraries (requests, langchain, web3). Adjust the endpoints and credentials for the platform you target (Upwork, Fiverr, Freelancer, etc.).
1. Architecture Overview
+----------------+ +----------------+ +----------------+
| Poller (cron) | ---> | LLM Chain | ---> | Platform API |
+----------------+ +----------------+ +----------------+
^ | |
| v v
+----------------+ +----------------+ +----------------+
| State Store | | Worker (run) | | x402 Payments |
+----------------+ +----------------+ +----------------+
-
Poller – a lightweight scheduler (e.g.,
APScheduleror a cloud cron) that queries the gig platform’s “new jobs” endpoint every N minutes. -
LLM Chain – a LangChain
LLMChainthat takes the job description, formats a prompt, and returns a proposal. - Platform API – the marketplace’s REST endpoints for fetching jobs, submitting proposals, and later delivering work.
- State Store – a tiny SQLite or Redis instance that records which job IDs have already been processed to avoid duplicate bids.
- Worker – the actual execution logic (here a stub that writes a Python file). In a real agent this could be a sandboxed container that runs the generated code.
-
x402 Payments – a microservice exposing an
/invoiceendpoint that returns a signed x402 payment request; the agent pays it with USDC on Base, and the service forwards the funds to the agent’s wallet after verifying the work artifact.
2. Setting Up the Environment
# Create a virtual environment
python -m venv venv
source venv/bin/activate
# Install dependencies
pip install requests langchain==0.1.0 web3==6.12.0 apscheduler==3.10.4
You’ll need:
- A platform API key / OAuth token (store it in an env var
PLATFORM_TOKEN). - An LLM provider – for this example we use OpenAI’s
gpt-4-turbo(OPENAI_API_KEY). - A wallet funded with USDC on Base (private key in
BASE_PRIVATE_KEY). - The address of the x402 payment service (
X402_SERVICE_URL).
export PLATFORM_TOKEN="your_platform_token"
export OPENAI_API_KEY="sk-..."
export BASE_PRIVATE_KEY="0x..."
export X402_SERVICE_URL="https://nexusai-x402.nikhilranka23.workers.dev"
3. Polling for New Gigs
Most platforms expose a JSON endpoint like GET /api/v1/jobs?status=open&skill=python. The poller fetches the list, filters out already‑seen IDs, and pushes each new job onto a queue (here a simple Python list).
import os, time, json, requests
from apscheduler.schedulers.blocking import BlockingScheduler
PLATFORM_URL = "https://api.example.com/v1/jobs"
HEADERS = {"Authorization": f"Bearer {os.getenv('PLATFORM_TOKEN')}"}
SEEN_FILE = "seen_jobs.json"
def load_seen():
if os.path.exists(SEEN_FILE):
return set(json.load(open(SEEN_FILE)))
return set()
def save_seen(seen):
json.dump(list(seen), open(SEEN_FILE, "w"))
def fetch_new_jobs():
seen = load_seen()
resp = requests.get(PLATFORM_URL, headers=HEADERS, timeout=10)
resp.raise_for_status()
data = resp.json() # assume list of job dicts
new_jobs = [j for j in data if j["id"] not in seen]
for job in new_jobs:
seen.add(job["id"])
save_seen(seen)
return new_jobs
scheduler = BlockingScheduler()
@scheduler.scheduled_job("interval", minutes=5)
def poll():
jobs = fetch_new_jobs()
if jobs:
print(f"[{time.strftime('%X')}] Found {len(jobs)} new job(s)")
for j in jobs:
handle_job(j) # defined later
else:
print(f"[{time.strftime('%X')}] No new jobs")
if __name__ == "__main__":
poll()
scheduler.start()
Trade‑off: Polling every 5 minutes is simple but introduces latency. For platforms that support webhooks, replace the scheduler with an HTTP listener to cut latency to near‑real‑time at the cost of exposing a public endpoint and managing signature verification.
4. Building the LLM Proposal Chain
We use LangChain’s PromptTemplate + LLMChain. The prompt instructs the model to produce a concise, professional proposal that mentions relevant experience, estimated timeline, and price.
from langchain import LLMChain, OpenAI, PromptTemplate
llm = OpenAI(temperature=0.2, model_name="gpt-4-turbo")
PROPOSAL_TEMPLATE = """
You are a freelance developer with expertise in {skill}.
A client posted the following job:
{description}
Write a short proposal (max 150 words) that:
1. Summarizes how you can solve the problem.
2. Highlights any relevant past work.
3. Gives an estimated delivery time and price in USD.
Do not include fluff or generic statements.
"""
prompt = PromptTemplate(
input_variables=["skill", "description"],
template=PROPOSAL_TEMPLATE,
)
proposal_chain = LLMChain(llm=llm, prompt=prompt)
def draft_proposal(job):
skill = job.get("required_skill", "Python")
description = job.get("description", "")
return proposal_chain.run({"skill": skill, "description": description})
Trade‑off: Using a high‑capacity model like GPT‑4 improves proposal quality but raises cost (~$0.03 per 1k tokens). If budget is tight, swap to a smaller model (e.g., gpt-3.5-turbo) and add a few‑shot example in the prompt to retain coherence.
5. Submitting the Proposal
Most platforms accept a JSON payload: { "job_id": "...", "cover_letter": "...", "bid_amount": 25 }. We extract the price from the LLM output via a simple regex; if parsing fails we fall back to a default.
import re
def extract_bid(text):
# Look for a number preceded by $ or the word "USD"
m = re.search(r'(?:\$|USD\s*)(\d+(?:\.\d+)?)', text, re.I)
return float(m.group(1)) if m else 20.0 # fallback
def submit_proposal(job, proposal):
bid = extract_bid(proposal)
payload = {
"job_id": job["id"],
"cover_letter": proposal,
"bid_amount": bid,
}
url = f"https://api.example.com/v1/proposals"
r = requests.post(url, headers=HEADERS, json=payload, timeout=10)
r.raise_for_status()
return r.json() # usually contains proposal_id
Trade‑off: Parsing monetary values from free‑form text is fragile. A more robust approach is to ask the model to output a JSON object ({"proposal": "...", "bid": 25}) and enforce it with a schema validator (e.g., pydantic). This adds a token overhead but reduces post‑processing bugs.
6. Performing the Work (Stub)
Once the platform marks a proposal as accepted (you can poll the /proposals/{id} endpoint for status), the agent proceeds to execute the task. For illustration we generate a simple Python script that prints “Hello, World!” and store it as an artifact.
def do_work(job):
# In a real agent you would fetch the detailed spec from the job description
# and possibly invoke a code‑generation model or run a test suite.
artifact = f"# Generated for job {job['id']}\nprint('Hello, World!')\n"
path = f"artifact_{job['id']}.py"
with open(path, "w") as f:
f.write(artifact)
return path
Trade‑off: The stub assumes the task is trivial. Real gigs often require running unit tests, building Docker images, or interacting with third‑party APIs. You’ll need a sandboxed execution environment (e.g., AWS Lambda, Fly.io, or a Kubernetes job) with resource limits, logging, and a way to return success/failure status to the platform.
7. Settling Payment via x402
The x402 standard lets you attach
Top comments (0)