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 fetch work, produce useful output, and get paid without manual intervention.


1. Why a chain, not a single call?

A gig platform usually expects a structured artifact (a proposal, a code snippet, a design mock‑up) that satisfies a set of constraints: length, tone, required skills, deadline, and sometimes a price quote. Feeding the raw job description straight into an LLM and hoping it returns a perfectly formatted proposal works only in toy demos. In practice you need:

  1. Input normalization – strip HTML, extract key fields, translate jargon.
  2. Prompt scaffolding – separate the “what” (job facts) from the “how” (style guide).
  3. Safety & validation – reject hallucinated skills, enforce character limits, check for prohibited content.
  4. Post‑processing – format the output to match the platform’s API (JSON, markdown, base64‑encoded file).

Chaining these steps makes each concern testable and replaceable. If the LLM provider changes, you only swap the generation; if the platform updates its schema, you adjust the serializer.


2. High‑level architecture

+----------------+    +----------------+    +----------------+    +----------------+
|   Gig Fetcher  | -->|  Prompt Builder| -->|   LLM Core     | -->|  Output Validator|
+----------------+    +----------------+    +----------------+    +----------------+
        ^                                                 |
        |                                                 v
+----------------+                                +----------------+
|   Rate Limiter |                                |  Platform API  |
+----------------+                                +----------------+
        ^                                                 |
        |                                                 v
+----------------+                                +----------------+
|   Payment Hook | <-- x402 micro‑payment <--|   Escrow/Leadger  |
+----------------+                                +----------------+
Enter fullscreen mode Exit fullscreen mode
  • Gig Fetcher – polls the platform’s public API (or a webhook) for new tasks that match a skill filter.
  • Rate Limiter – guards against platform throttling and LLM provider quotas (token‑bucket algorithm).
  • Prompt Builder – assembles a deterministic prompt from a template and the fetched task fields.
  • LLM Core – calls a completion endpoint (OpenAI, Anthropic, or a self‑hosted model).
  • Output Validator – runs regex/JSON‑schema checks, strips disallowed content, and falls back to a retry or a human‑in‑the‑loop queue.
  • Platform API – POSTs the validated artifact (proposal, code patch, design file) back to the gig site.
  • Payment Hook – triggers an x402 micro‑payment when the platform acknowledges receipt; the agent receives USDC on Base.

3. Working code snippets

Below is a minimal, production‑ready skeleton in Python 3.11 using httpx, tenacity, pydantic, and langchain (the LLM abstraction layer). Adjust imports and credentials for your environment.


python
# -------------------------------------------------
# 1. Gig fetcher (example: generic JSON endpoint)
# -------------------------------------------------
import os
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential

GIG_FEED_URL = os.getenv("GIG_FEED_URL")  # e.g. https://api.example.com/v1/open-gigs
SKILL_FILTER = {"python", "llm", "automation"}

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def fetch_gigs() -> list[dict]:
    r = httpx.get(GIG_FEED_URL, timeout=10.0)
    r.raise_for_status()
    data = r.json()
    # Assume each gig has: id, title, description, required_skills[], budget_usd
    return [
        g for g in data
        if SKILL_FILTER.intersection(set(g.get("required_skills", [])))
    ]


# -------------------------------------------------
# 2. Prompt builder (template + field injection)
# -------------------------------------------------
from jinja2 import Template

PROMPT_TEMPLATE = Template("""
You are a professional freelancer. Write a concise proposal (max 250 words) for the following job.

Job ID: {{ gig.id }}
Title: {{ gig.title }}
Description: {{ gig.description }}
Required skills: {{ gig.required_skills | join(", ") }}
Budget (USD): {{ gig.budget_usd }}

Your proposal must:
- Address the client by name if provided, otherwise use "Hi there".
- Summarize how your expertise matches each required skill.
- State a realistic timeline and price (do not exceed the budget).
- End with a clear call‑to‑action: "Let's discuss next steps."

Do not mention that you are an AI. Do not fabricate credentials or past work.
""")

def build_prompt(gig: dict) -> str:
    return PROMPT_TEMPLATE.render(gig=gig)


# -------------------------------------------------
# 3. LLM core (langchain wrapper)
# -------------------------------------------------
from langchain.chat_models import ChatOpenAI
from langchain.schema import HumanMessage

llm = ChatOpenAI(
    model_name="gpt-4o-mini",
    temperature=0.3,
    max_tokens=400,
    openai_api_key=os.getenv("OPENAI_API_KEY"),
)

def generate_proposal(prompt: str) -> str:
    response = llm([HumanMessage(content=prompt)])
    return response.content.strip()


# -------------------------------------------------
# 4. Output validator (pydantic + regex)
# -------------------------------------------------
import re
from pydantic import BaseModel, Field, validator

class Proposal(BaseModel):
    text: str = Field(..., min_length=50, max_length=300)
    has_cta: bool = Field(False)

    @validator("text")
    def no_hallucinated_skills(cls, v):
        # Simple check: reject phrases like "I have 10 years of experience in quantum computing"
        # if not in required_skills (this would need context; shown as illustration)
        forbidden = ["quantum computing", "blockchain", "nuclear physics"]
        for f in forbidden:
            if f.lower() in v.lower():
                raise ValueError(f"Hallucinated skill detected: {f}")
        return v

    @validator("has_cta", always=True)
    def detect_cta(cls, v, values):
        txt = values.get("text", "")
        # Very naive CTA detection – replace with a proper intent model if needed
        return bool(re.search(r"\b(let'?s|discuss|next steps|contact|meet)\b", txt, re.I))

def validate_and_fix(raw: str, gig: dict) -> Proposal | None:
    try:
        prop = Proposal(text=raw)
        return prop
    except Exception as e:
        # Optional: one-shot repair prompt
        repair_prompt = f"The previous proposal failed validation: {e}.\\nPlease rewrite it obeying the constraints."
        repaired = generate_proposal(build_prompt(gig) + "\n\n" + repair_prompt)
        try:
            return Proposal(text=repaired)
        except Exception:
            return None   # send to dead‑letter queue for human review


# -------------------------------------------------
# 5. Platform API submission (example pseudo‑endpoint)
# -------------------------------------------------
def submit_proposal(gig_id: str, proposal_text: str) -> bool:
    url = f"https://api.example.com/v1/gigs/{gig_id}/proposals"
    payload = {"content": proposal_text, "format": "plain"}
    headers = {"Authorization": f"Bearer {os.getenv('PLATFORM_TOKEN')}"}
    r = httpx.post(url, json=payload, headers=headers, timeout=10.0)
    if r.status_code == 201:
        return True
    # Handle 429 (rate limit) via the outer tenacity retry decorator if desired
    return False


# -------------------------------------------------
# 6. Payment hook – x402 micro‑payment
# -------------------------------------------------
# x402 is a HTTP 402 Payment Required extension. The client (our agent) receives
# a 402 response with a payment request; we fulfill it using a lightweight
# library like `x402-py` (not shown for brevity). On success we get a USDC
# receipt on Base.
def request_micropayment(amount_usdc: float) -> str:
    # Pseudo‑code: call x402 middleware, sign with agent's wallet, return tx hash
    raise NotImplementedError("Integrate your x402 provider here")


# -------------------------------------------------
# 7. Main loop (simplified)
# -------------------------------------------------
import time
from ratelimit import limits, sleep_and_retry

# Allow max 1 gig fetch per 5 seconds to stay polite to the source
@sleep_and_retry
@limits(calls=1, period=5)
def poll_and_process():
    for gig in fetch_gigs():
        prompt = build_prompt(gig)
        raw = generate_proposal(prompt)
        proposal = validate_and_fix(raw, gig)
        if proposal is None:
            continue  # skip to next gig after logging
        if not submit_proposal(gig["id"], proposal.text):
            continue  # let outer retry handle transient errors
        # Assuming the platform replies with HTTP 402 containing payment request
        # In practice you would inspect the response headers for a Payment
Enter fullscreen mode Exit fullscreen mode

Top comments (0)