Run Your First Pi‑AI Agent in Under 10 Minutes

This tutorial walks you through preparing the environment, initializing a Node.js project, writing the first Pi‑AI agent code, using both simple and streaming calls, swapping providers with a single parameter change, and building a continuous‑conversation CLI—all in less than ten minutes.

CodePath
CodePath
CodePath
Run Your First Pi‑AI Agent in Under 10 Minutes

Environment preparation

Check three things: Node.js 20+, an API key (Anthropic, OpenAI, Google, DeepSeek, etc.), and a code editor (VS Code). Export the key as an environment variable, e.g. export ANTHROPIC_API_KEY=sk‑…, so Pi reads it automatically.

Project initialization

Create a folder, run npm init -y, then install the Pi SDK and TypeScript runner:

npm install @mariozechner/pi-ai
npm install -D tsx typescript

Ensure package.json contains "type":"module" and the devDependencies above. Add a tsconfig.json with target ES2022, module ESNext, moduleResolution "bundler", and strict mode enabled.

First agent call (completeSimple)

Create first-step.ts and paste the following code:

import { getModel, completeSimple } from "@mariozechner/pi-ai";

async function main() {
  const model = getModel("deepseek", "deepseek-v4-pro");
  const response = await completeSimple(model, {
    systemPrompt: "你是一个简洁的编码助手,回答不超过三句话。",
    messages: [{ role: "user", content: "TypeScript 里的类型守卫是什么?", timestamp: Date.now() }]
  });
  for (const block of response.content) {
    if (block.type === "text") {
      console.log("
🤖 回答:
");
      console.log(block.text);
    }
  }
  console.log(`
📊 输入 Tokens:${response.usage.input}`);
  console.log(`📊 输出 Tokens:${response.usage.output}`);
  console.log(`⏹️  停止原因:${response.stopReason}`);
}

main().catch(console.error);

Run it with npx tsx first-step.ts after exporting DEEPSEEK_API_KEY. The terminal prints a concise answer, token usage, and the stop reason.

Streaming output (streamSimple)

Replace completeSimple with streamSimple in stream-step.ts:

import { getModel, streamSimple } from "@mariozechner/pi-ai";

async function main() {
  const model = getModel("anthropic", "claude-sonnet-4-20250514");
  console.log("🤖 流式输出:
");
  const stream = streamSimple(model, {
    systemPrompt: "你是一个简洁的编码助手,回答不超过三句话。",
    messages: [{ role: "user", content: "用一句话解释什么是 Promise。", timestamp: Date.now() }]
  });
  for await (const event of stream) {
    switch (event.type) {
      case "text_delta":
        process.stdout.write(event.delta);
        break;
      case "done":
        console.log(`

📊 Tokens:${event.message.usage.output} 输出`);
        break;
      case "error":
        console.error("
❌ 错误:", event.error.errorMessage);
        break;
    }
  }
}

main().catch(console.error);

Running the script shows the response character‑by‑character, then prints the token count.

Switching providers

The SDK abstracts providers via getModel(provider, modelName). Changing only the first argument swaps Claude, GPT‑5‑mini, Gemini, Groq, DeepSeek, etc., while the rest of the code (calls to completeSimple or streamSimple) stays unchanged. The returned Model object contains a fixed structure: API base URL, request protocol, context window size, and capability list (tools, vision, reasoning).

Continuous conversation

A more advanced example keeps a messages array, uses node:readline/promises for interactive input, and calls streamSimple inside a loop. Each user input is appended to the history, the model replies via streaming, and the reply is added back to the history so subsequent turns retain context. Typing “exit” ends the loop.

import { getModel, streamSimple } from "@mariozechner/pi-ai";
import * as readline from "node:readline/promises";

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const model = getModel("deepseek", "deepseek-v4-pro");
const messages: any[] = [];

async function askLLM(userInput: string) {
  messages.push({ role: "user", content: userInput, timestamp: Date.now() });
  const stream = streamSimple(model, { systemPrompt: "你是一个友好的编程助手。", messages });
  let fullReply = "";
  process.stdout.write("
🤖 ");
  for await (const event of stream) {
    if (event.type === "text_delta") {
      process.stdout.write(event.delta);
      fullReply += event.delta;
    } else if (event.type === "done") {
      messages.push(event.message);
    } else if (event.type === "error") {
      console.error("
❌", event.error.errorMessage);
      return;
    }
  }
  console.log();
}

async function main() {
  console.log("💬 多轮对话(输入 exit 退出)
");
  while (true) {
    const input = await rl.question("你 > ");
    if (input.toLowerCase() === "exit") break;
    await askLLM(input);
  }
  rl.close();
}

main().catch(console.error);

What’s next

The article points to the pi-ai source for deeper inspection of getModel, completeSimple, the provider adapters, request serialization, and the higher‑level pi-agent-core library, helping you understand the minimal LLM abstraction layer.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

TypeScriptLLMStreamingNode.jsModel Switchingpi-ai
CodePath
Written by

CodePath

Focused on specific functional points, dedicated to concise, high-quality content, covering Java development, Linux source code, Spring source code, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.