Jinba Toolbox
Guides

Tool Development Guide

How to develop tools in Python/TypeScript

Basic Structure

A Tool consists of Input, Output, and a run function.

Python

Define schemas using Pydantic's BaseModel.

from pydantic import BaseModel, Field

class Input(BaseModel):
    url: str = Field(description="URL to fetch")

class Output(BaseModel):
    title: str
    content: str

def run(input: Input) -> Output:
    # Implement your logic
    return Output(title="...", content="...")

TypeScript

Define schemas using Zod.

import { z } from "zod";

export const input = z.object({
  url: z.string().describe("URL to fetch"),
});

export const output = z.object({
  title: z.string(),
  content: z.string(),
});

export async function run(data: z.infer<typeof input>) {
  // Implement your logic
  return { title: "...", content: "..." };
}

External Libraries

Add packages in the ToolSet settings to import them in your code.

import requests  # Add "requests" in package settings
from bs4 import BeautifulSoup  # Add "beautifulsoup4"

def run(input: Input) -> Output:
    html = requests.get(input.url).text
    soup = BeautifulSoup(html, "html.parser")
    return Output(title=soup.title.string)

Version specification is also supported (e.g., requests==2.31.0).

Environment Variables

Pass sensitive information like API keys via environment variables. Register them in the ToolSet settings.

import os

def run(input: Input) -> Output:
    api_key = os.environ["SLACK_API_KEY"]
    # Use api_key...
export async function run(data: z.infer<typeof input>) {
  const apiKey = process.env.SLACK_API_KEY;
  // Use apiKey...
}

Environment Variables

Error Handling

Throwing an exception will record the error information.

def run(input: Input) -> Output:
    if not input.url.startswith("https://"):
        raise ValueError("URL must start with https://")
    # ...

Practical Examples

Slack Notification

import os
import requests
from pydantic import BaseModel, Field

class Input(BaseModel):
    channel: str = Field(description="Target channel")
    message: str = Field(description="Message body")

class Output(BaseModel):
    success: bool
    ts: str = Field(description="Message timestamp")

def run(input: Input) -> Output:
    response = requests.post(
        "https://slack.com/api/chat.postMessage",
        headers={"Authorization": f"Bearer {os.environ['SLACK_BOT_TOKEN']}"},
        json={"channel": input.channel, "text": input.message},
    )
    data = response.json()
    if not data["ok"]:
        raise Exception(data["error"])
    return Output(success=True, ts=data["ts"])

Web Page Fetching

import requests
from bs4 import BeautifulSoup
from pydantic import BaseModel

class Input(BaseModel):
    url: str

class Output(BaseModel):
    title: str
    description: str
    text: str

def run(input: Input) -> Output:
    html = requests.get(input.url).text
    soup = BeautifulSoup(html, "html.parser")

    title = soup.title.string if soup.title else ""
    desc_tag = soup.find("meta", {"name": "description"})
    description = desc_tag["content"] if desc_tag else ""
    text = soup.get_text()[:2000]

    return Output(title=title, description=description, text=text)

External API Call

import os
import requests
from pydantic import BaseModel

class Input(BaseModel):
    query: str

class Output(BaseModel):
    results: list[dict]

def run(input: Input) -> Output:
    response = requests.get(
        "https://api.example.com/search",
        headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
        params={"q": input.query},
    )
    response.raise_for_status()
    return Output(results=response.json()["results"])

Schema Best Practices

Add Descriptions

Descriptions help AI choose the right tool.

class Input(BaseModel):
    query: str = Field(description="Search keyword")
    limit: int = Field(default=10, description="Max results (1-100)")
export const input = z.object({
  query: z.string().describe("Search keyword"),
  limit: z.number().default(10).describe("Max results (1-100)"),
});

Optional Types

from typing import Optional

class Input(BaseModel):
    query: str
    filter: Optional[str] = None  # Optional

Nested Types

class Address(BaseModel):
    city: str
    country: str = "JP"

class Input(BaseModel):
    name: str
    address: Address

Testing

Enter Input in the test panel at the bottom and run with Cmd+Enter.

You can test draft code without publishing, which is convenient for development.

On this page