Tenzro Testnet is live —request testnet TNZO
← Back to Tutorials
AI AgentsIntermediate

Build an AI Coding Assistant

Build an AI-powered coding assistant that discovers models on the Tenzro network, streams inference responses, pays for inference with TNZO micropayments, and deploys as a reusable agent on the network. Your assistant will have skills like code generation, debugging, and explanation -- all backed by decentralized AI models.

What We're Building

Prerequisites

  • Tenzro SDK installed (cargo add tenzro-sdk or npm install tenzro-sdk)
  • A funded wallet on testnet (100 TNZO from faucet)
  • Basic understanding of AI inference and chat completions

Estimated time: 30 minutes

Step 1: Discover Available Models

First, discover what AI models are available on the network. Models are registered in the model registry and served by providers who earn TNZO:

Rust

use tenzro_sdk::{TenzroClient, config::SdkConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = SdkConfig::testnet();
    let client = TenzroClient::connect(config).await?;
    let inference = client.inference();

    // List all available models
    let models = inference.list_models().await?;
    println!("Available models:");
    for model in &models {
        println!("  {} ({}) - {} params - {}",
            model.model_id,
            model.name,
            model.parameters.unwrap_or_default(),
            model.status,
        );
    }

    // Filter by category
    let code_models = inference.list_models_filtered(Some("code"), None).await?;
    println!("Code models: {}", code_models.len());

    Ok(())
}

TypeScript

import { TenzroClient, TESTNET_CONFIG } from "tenzro-sdk";

const client = new TenzroClient(TESTNET_CONFIG);

// List all models
const models = await client.inference.listModels();
console.log("Available models:");
for (const model of models) {
  console.log(`  ${model.model_id} - ${model.name} (${model.status})`);
}

Example output:

Available models:
  gemma3-270m (Gemma 3 270M) - 270M params - available
  qwen3.5-0.8b (Qwen 3.5 0.8B) - 800M params - available
  qwen3-4b (Qwen 3 4B) - 4B params - available
  phi-4-mini (Phi 4 Mini) - 3.8B params - available
  gemma4-9b (Gemma 4 9B) - 9B params - available

Step 2: Run Basic Inference

Send a coding question to a model and get a response:

Rust

// Simple inference request
let response = inference.request(
    "qwen3.5-0.8b",
    "Write a Python function that reverses a linked list in-place.",
    Some(500),  // max tokens
).await?;

println!("Response:");
println!("{}", response.output);
println!("Tokens: {}", response.tokens_used);
println!("Cost: {} TNZO", response.cost);

TypeScript

const response = await client.inference.request(
  "qwen3.5-0.8b",
  "Write a Python function that reverses a linked list in-place.",
  500,
);

console.log("Response:", response.output);
console.log("Tokens:", response.tokens_used);
console.log("Cost:", response.cost, "TNZO");

Step 3: Stream Responses in Real-Time

For a better user experience, stream tokens as they are generated. The StreamingClient uses SSE (Server-Sent Events) to deliver tokens in real-time:

Rust

let streaming = client.streaming();

// Stream chat completion
let result = streaming.chat_stream(
    "qwen3.5-0.8b",
    "Explain the difference between a mutex and a semaphore with Rust examples.",
    Some(800),
    |token| {
        // This callback fires for each generated token
        print!("{}", token);
        std::io::Write::flush(&mut std::io::stdout()).unwrap();
    },
).await?;

println!("\n\nTotal tokens: {}", result.total_tokens);
println!("Complete output length: {}", result.output.len());

TypeScript

// Stream chat completion
const result = await client.streaming.chatStream(
  "qwen3.5-0.8b",
  "Explain the difference between a mutex and a semaphore with Rust examples.",
  800,
  (token: string) => {
    // Print each token as it arrives
    process.stdout.write(token);
  },
);

console.log("\nTotal tokens:", result.total_tokens);

Step 4: Build the Coding Assistant Class

Wrap the inference and streaming capabilities into a reusable coding assistant class with specialized skills:

import { TenzroClient, TESTNET_CONFIG } from "tenzro-sdk";

type Skill = "generate" | "debug" | "explain" | "review" | "refactor";

const SYSTEM_PROMPTS: Record<Skill, string> = {
  generate: "You are an expert programmer. Generate clean, well-documented code based on the user's requirements. Include type annotations and error handling.",
  debug: "You are a debugging expert. Analyze the provided code, identify bugs, explain the root cause, and provide a corrected version.",
  explain: "You are a patient teacher. Explain the provided code or concept clearly, with examples. Adjust complexity to the user's level.",
  review: "You are a senior code reviewer. Review the code for bugs, performance issues, security vulnerabilities, and style. Be specific and actionable.",
  refactor: "You are a refactoring expert. Improve the code's structure, readability, and performance while preserving behavior. Explain each change.",
};

class CodingAssistant {
  private client: TenzroClient;
  private modelId: string;
  private history: Array<{ role: string; content: string }> = [];

  constructor(client: TenzroClient, modelId: string = "qwen3.5-0.8b") {
    this.client = client;
    this.modelId = modelId;
  }

  async ask(skill: Skill, prompt: string): Promise<string> {
    const systemPrompt = SYSTEM_PROMPTS[skill];
    const fullPrompt = `System: ${systemPrompt}\n\nUser: ${prompt}`;

    const response = await this.client.inference.request(
      this.modelId,
      fullPrompt,
      1000,
    );

    this.history.push(
      { role: "user", content: prompt },
      { role: "assistant", content: response.output },
    );

    return response.output;
  }

  async stream(skill: Skill, prompt: string, onToken: (t: string) => void): Promise<string> {
    const systemPrompt = SYSTEM_PROMPTS[skill];
    const fullPrompt = `System: ${systemPrompt}\n\nUser: ${prompt}`;

    const result = await this.client.streaming.chatStream(
      this.modelId,
      fullPrompt,
      1000,
      onToken,
    );

    this.history.push(
      { role: "user", content: prompt },
      { role: "assistant", content: result.output },
    );

    return result.output;
  }

  getHistory() {
    return this.history;
  }

  clearHistory() {
    this.history = [];
  }
}

// Usage
async function main() {
  const client = new TenzroClient(TESTNET_CONFIG);
  const assistant = new CodingAssistant(client, "qwen3.5-0.8b");

  // Generate code
  const code = await assistant.ask(
    "generate",
    "Write a TypeScript function that implements binary search on a sorted array.",
  );
  console.log("Generated code:", code);

  // Stream a code review
  console.log("\nCode review:");
  await assistant.stream(
    "review",
    code,
    (token) => process.stdout.write(token),
  );

  // Debug some code
  const buggyCode = `function sum(arr) {
  let total = 0;
  for (let i = 0; i <= arr.length; i++) {
    total += arr[i];
  }
  return total;
}`;

  const debugResult = await assistant.ask("debug", buggyCode);
  console.log("\n\nDebug result:", debugResult);
}

main().catch(console.error);

Step 5: Pay for Inference with Micropayments

For production use, set up a micropayment channel to pay for inference per token without on-chain transactions for each call:

Rust

let settlement = client.settlement();

// Open a micropayment channel with 10 TNZO
let channel_id = settlement.open_channel(
    "0xProviderAddress1234567890abcdef12345678", // inference provider
    10_000_000_000_000_000_000,                    // 10 TNZO locked
    86400,                                          // 24h dispute period
).await?;
println!("Channel opened: {}", channel_id);

// Each inference request sends a signed update to the channel
// instead of an on-chain transaction
let response = inference.request_with_channel(
    "qwen3.5-0.8b",
    "Write a Rust async TCP server.",
    Some(500),
    &channel_id,
).await?;
println!("Response: {}", response.output);
println!("Channel balance remaining: {}", response.channel_balance);

// Close channel when done (settles on-chain once)
let close = settlement.close_channel(&channel_id).await?;
println!("Channel closed. Final cost: {} TNZO", close.total_spent);

TypeScript

// Open micropayment channel
const channelId = await client.settlement.openChannel(
  "0xProviderAddress1234567890abcdef12345678",
  10_000_000_000_000_000_000n,  // 10 TNZO
  86400,                          // 24h dispute period
);
console.log("Channel:", channelId);

// Use channel for inference payments
const response = await client.inference.requestWithChannel(
  "qwen3.5-0.8b",
  "Write a Rust async TCP server.",
  500,
  channelId,
);
console.log("Response:", response.output);

// Close channel
const close = await client.settlement.closeChannel(channelId);
console.log("Total spent:", close.total_spent, "TNZO");

Micropayment Channels vs MPP

MPP is session-based: open a session, accumulate charges, settle once at the end. Good for single conversations. Micropayment channels lock funds in escrow and allow unlimited off-chain updates. Good for long-running services like a coding assistant that might handle hundreds of requests per day. Channels settle on-chain only at open and close -- everything in between is gasless.

Step 6: Register as a Network Agent

Deploy the coding assistant as a network agent so other agents and apps can discover and use it:

// Register the coding assistant as an agent
const agentResult = await client.agent.register(
  "coding-assistant-v1",
  "Tenzro Coding Assistant",
  ["code-generation", "debugging", "code-review", "refactoring", "explanation"],
);
console.log("Agent registered:", agentResult.agent_id);
console.log("Wallet:", agentResult.wallet_address);

// Register as an agent template in the marketplace
await client.marketplace().registerTemplate({
  name: "Coding Assistant",
  description: "AI-powered coding assistant with code generation, debugging, review, and refactoring capabilities.",
  template_type: "service",
  capabilities: [
    { name: "code-generation", description: "Generate code from natural language" },
    { name: "debugging", description: "Find and fix bugs in code" },
    { name: "code-review", description: "Review code for quality and security" },
    { name: "refactoring", description: "Improve code structure and performance" },
  ],
  pricing: {
    model: "per_request",
    base_price: "0.01",
    currency: "TNZO",
  },
  runtime_requirements: {
    min_memory_mb: 512,
    requires_gpu: false,
    requires_tee: false,
  },
});
console.log("Agent template published to marketplace");

Step 7: Accept Tasks from Other Agents

Once deployed, your agent can receive tasks via the A2A protocol:

// Listen for incoming tasks
const tasks = await client.task().listTasks({
  assignee: agentResult.agent_id,
  status: "pending",
});

for (const task of tasks) {
  console.log("Received task:", task.task_id);
  console.log("  Description:", task.description);

  // Process the task with the coding assistant
  const assistant = new CodingAssistant(client, "qwen3.5-0.8b");
  const result = await assistant.ask("generate", task.description);

  // Complete the task
  await client.task().completeTask(task.task_id, result);
  console.log("Task completed:", task.task_id);
}

Complete Example

import { TenzroClient, TESTNET_CONFIG } from "tenzro-sdk";

async function main() {
  const client = new TenzroClient(TESTNET_CONFIG);

  // 1. Find a suitable model
  const models = await client.inference.listModels();
  const model = models.find(m => m.status === "available") || models[0];
  console.log("Using model:", model.model_id);

  // 2. Generate code
  const response = await client.inference.request(
    model.model_id,
    "Write a TypeScript class for a thread-safe queue with enqueue, dequeue, and peek methods.",
    800,
  );
  console.log("Generated code:");
  console.log(response.output);
  console.log("Cost:", response.cost, "TNZO");

  // 3. Stream a code review
  console.log("\nStreaming code review...");
  await client.streaming.chatStream(
    model.model_id,
    "Review this code for thread safety issues:\n" + response.output,
    500,
    (token) => process.stdout.write(token),
  );

  // 4. Register as agent
  const agent = await client.agent.register(
    "my-coding-assistant",
    "My Coding Assistant",
    ["code-generation", "debugging", "review"],
  );
  console.log("\n\nAgent deployed:", agent.agent_id);
}

main().catch(console.error);

What You Learned

Next Steps

Build More with Tenzro

Explore AI model integration and agent capabilities in the documentation.