# Vercel AI SDK + RentAHuman

Build Next.js AI applications with Vercel AI SDK that hire humans via RentAHuman. Add real-world task delegation to your AI chat interfaces and APIs.

## Vercel AI SDK + RentAHuman Integration

The Vercel AI SDK is the standard way to build AI-powered web applications with Next.js. It provides streaming responses, tool calling, and a React hooks API that makes building chat interfaces effortless. By adding RentAHuman as a tool provider, your web application's AI assistant can hire real humans for physical tasks directly from the chat interface.

### Why Vercel AI SDK + RentAHuman

The Vercel AI SDK is designed for production web applications. It handles streaming, error states, loading indicators, and multi-step tool calls out of the box. RentAHuman is itself built on Next.js and deployed on Vercel, so the integration is especially clean. Your users get a chat experience where they describe what they need, the AI finds qualified humans, and the hiring happens without leaving your app.

### Installation

```bash
npm install ai @ai-sdk/openai zod
```

### Server-Side: Define Tools in a Route Handler

```typescript
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText, tool } from "ai";
import { z } from "zod";
const RENTAHUMAN_API = "https://rentahuman.ai/api";
const API_KEY = process.env.RENTAHUMAN_API_KEY!;
export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: openai("gpt-4o"),
    system: `You are a helpful assistant with access to the RentAHuman marketplace. When users need something done in the physical world, use the available tools to search for humans, post bounties, and manage the hiring process. Always confirm budget and task details before posting a bounty.`,
    messages,
    tools: {
      searchHumans: tool({
        description: "Search the RentAHuman marketplace for humans available to hire by skills and location",
        parameters: z.object({
          query: z.string().describe("Skills or task description"),
          location: z.string().optional().describe("City or area"),
          maxRate: z.number().optional().describe("Maximum hourly rate in USD"),
        }),
        execute: async ({ query, location, maxRate }) => {
          const params = new URLSearchParams({ q: query, limit: "10" });
          if (location) params.set("location", location);
          if (maxRate) params.set("maxRate", String(maxRate));
          const resp = await fetch(`${RENTAHUMAN_API}/search?${params}`, {
            headers: { "x-api-key": API_KEY },
          });
          return await resp.json();
        },
      }),
      getHumanProfile: tool({
        description: "Get the full profile of a specific human on RentAHuman",
        parameters: z.object({
          humanId: z.string().describe("The human's profile ID"),
        }),
        execute: async ({ humanId }) => {
          const resp = await fetch(`${RENTAHUMAN_API}/humans/${humanId}`, {
            headers: { "x-api-key": API_KEY },
          });
          return await resp.json();
        },
      }),
      createBounty: tool({
        description: "Post a task bounty on RentAHuman for humans to apply to",
        parameters: z.object({
          title: z.string().describe("Short task title"),
          description: z.string().describe("Detailed requirements"),
          payAmount: z.number().describe("Payment in USD"),
          location: z.string().optional().describe("Where the task happens"),
        }),
        execute: async ({ title, description, payAmount, location }) => {
          const resp = await fetch(`${RENTAHUMAN_API}/bounties`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "x-api-key": API_KEY,
            },
            body: JSON.stringify({
              title,
              description,
              payAmount,
              payType: "fixed",
              location: location || "",
              isRemote: !location,
            }),
          });
          return await resp.json();
        },
      }),
      startConversation: tool({
        description: "Start a conversation with a human to discuss a task",
        parameters: z.object({
          humanId: z.string().describe("The human's profile ID"),
          message: z.string().describe("Initial message"),
        }),
        execute: async ({ humanId, message }) => {
          const resp = await fetch(`${RENTAHUMAN_API}/conversations`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "x-api-key": API_KEY,
            },
            body: JSON.stringify({ humanId, message }),
          });
          return await resp.json();
        },
      }),
    },
    maxSteps: 5,
  });
  return result.toDataStreamResponse();
}
```

### Client-Side: Chat Interface with useChat

```tsx
// app/page.tsx
"use client";
import { useChat } from "ai/react";
export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } =
    useChat({ api: "/api/chat" });
  return (
    <div className="max-w-2xl mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">AI Assistant + RentAHuman</h1>
      <div className="space-y-4 mb-4">
        {messages.map((m) => (
          <div
            key={m.id}
            className={m.role === "user" ? "text-right" : "text-left"}
          >
            <div
              className={`inline-block p-3 rounded-lg ${
                m.role === "user"
                  ? "bg-blue-600 text-white"
                  : "bg-muted text-foreground"
              }`}
            >
              {m.content}
              {/* Show tool invocations */}
              {m.toolInvocations?.map((tool, i) => (
                <div key={i} className="mt-2 text-xs text-muted-foreground border-t border-input pt-2">
                  <span className="font-mono">Tool: {tool.toolName}</span>
                  {"result" in tool && (
                    <pre className="mt-1 overflow-auto">
                      {JSON.stringify(tool.result, null, 2)}
                    </pre>
                  )}
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Describe a task you need done..."
          className="flex-1 p-3 rounded-lg bg-card text-foreground border border-input"
        />
        <button
          type="submit"
          disabled={isLoading}
          className="px-4 py-3 bg-primary text-white rounded-lg disabled:opacity-50"
        >
          {isLoading ? "..." : "Send"}
        </button>
      </form>
    </div>
  );
}
```

### Server Actions (Alternative Pattern)

```typescript
// app/actions.ts
"use server";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function delegateTask(taskDescription: string) {
  const { text, toolResults } = await generateText({
    model: openai("gpt-4o"),
    tools: {
      // ... same tool definitions as above
    },
    prompt: `Find and hire someone for this task: ${taskDescription}`,
    maxSteps: 5,
  });
  return { text, toolResults };
}
```

### Common Use Cases

- **AI-powered customer support** — Chat interface where agents can dispatch humans for on-site assistance
- **Task management apps** — Users describe tasks in natural language, AI handles human sourcing
- **Internal tools** — Company dashboard where employees request physical-world tasks via AI chat
- **SaaS with human fallback** — When your AI hits its limits, seamlessly delegate to a human via RentAHuman

### Best Practices

- **Use `maxSteps`** — Set `maxSteps: 5` or higher to allow the AI to make multiple tool calls in sequence (search, then get profile, then create bounty).
- **Show tool invocations** — Display tool calls in the chat UI so users can see what the AI is doing. The `toolInvocations` property on messages makes this easy.
- **Validate on the server** — Never pass API keys to the client. All RentAHuman API calls should happen in route handlers or server actions.
- **Stream responses** — Use `streamText` and `useChat` for the best user experience. Tool call results appear incrementally.
- **Add confirmation steps** — For actions that cost money (creating bounties, accepting applicants), use a confirmation tool that asks the user before proceeding.
