Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: initial commit of developer information about tools #7

Merged
merged 21 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
52dfd3e
docs: initial commit of developer information about tools
grahamwhiteuk Aug 28, 2024
095f29d
fix: linting errors
grahamwhiteuk Aug 29, 2024
9a1a645
fix: example code indentation
grahamwhiteuk Aug 29, 2024
7136859
docs: make references to Tool and DynamicTool more accurate
grahamwhiteuk Aug 30, 2024
484d9b1
docs: best practice to set tool name property to the name of the class
grahamwhiteuk Aug 30, 2024
644c240
docs: make the inputSchema description more complete
grahamwhiteuk Aug 30, 2024
5232b5c
docs: add serializable note
grahamwhiteuk Aug 30, 2024
ba71fe2
docs: clarify class parameters
grahamwhiteuk Aug 30, 2024
6086148
docs: use full path name instead of aliases when referencing imports
grahamwhiteuk Aug 30, 2024
a98b64f
fix: inputSchema specification, name and extraneous options
grahamwhiteuk Aug 30, 2024
e82c387
fix: camel case file names
grahamwhiteuk Aug 30, 2024
171e59b
fix: typo
grahamwhiteuk Aug 30, 2024
01e0dba
fix: inputSchema as a function
grahamwhiteuk Aug 30, 2024
c645336
fix: linting error
grahamwhiteuk Aug 30, 2024
62cd567
docs: camel case and correction of file names
grahamwhiteuk Aug 30, 2024
c796ca5
docs: add link to tools documentation
grahamwhiteuk Aug 30, 2024
6ed783c
docs: add import path to code example
grahamwhiteuk Aug 30, 2024
8e59055
fix: replace statusText with response.text()
grahamwhiteuk Aug 30, 2024
adec4d4
docs: remove js extension and add newlines
grahamwhiteuk Sep 2, 2024
503d905
chore: trigger build
grahamwhiteuk Sep 2, 2024
e8e13c5
chore: trigger build
grahamwhiteuk Sep 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
<a aria-label="Join the community on GitHub" href="https://github.com/i-am-bee/bee-agent-framework/discussions">
<img alt="" src="https://img.shields.io/badge/Join%20the%20community-blueviolet.svg?style=for-the-badge&labelColor=000000&label=Bee">
</a>
<h4 align="center">Open-source framework for building, deploying, and serving powerful agentic workflows at scale.</h4>
<h4 align="center">Open-source framework for building, deploying, and serving powerful agentic workflows at scale.</h4>
</p>

The Bee framework makes it easy to build agentic worfklows with leading open-source and proprietary models. We’re working on bringing model-agnostic support to any LLM to help developers avoid model provider lock-in.

## Key Features

- 🤖 **AI agents**: Use our powerful [Bee agent](https://github.com/i-am-bee/bee-agent-framework/tree/main?tab=readme-ov-file#get-started-with-bee) or [build your own](https://github.com/i-am-bee/bee-agent-framework/blob/main/docs/overview.md#agents).
- 🛠️ **Tools**: Use our [built-in tools](https://github.com/i-am-bee/bee-agent-framework/blob/main/docs/overview.md#tools) or create your own in Javascript/Python.
- 🛠️ **Tools**: Use our [built-in tools](https://github.com/i-am-bee/bee-agent-framework/blob/main/docs/overview.md#tools) or [create your own](https://github.com/i-am-bee/bee-agent-framework/blob/main/docs/tools.md) in Javascript/Python.
- 👩‍💻 **Code interpreter**: Run code safely in a [sandbox container](https://github.com/i-am-bee/bee-code-interpreter).
- 💾 **Memory**: Multiple [strategies](https://github.com/i-am-bee/bee-agent-framework/blob/main/docs/overview.md#memory) to optimize token spend.
- ⏸️ **Serialization** Handle complex agentic workflows and easily pause/resume them [without losing state](https://github.com/i-am-bee/bee-agent-framework/blob/main/docs/overview.md#serializer).
Expand Down
2 changes: 1 addition & 1 deletion docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ The framework provides out-of-the-box tools.
| `OpenMeteoTool` | Retrieves current, previous, or upcoming weather for a given destination. |
|[Request](https://github.com/i-am-bee/bee-agent-framework/discussions) | |

To create your own tool, you need to either implement the `BaseTool` class ([example](../examples/tools/customHttpRequest.ts)) or use `DynamicTool.`
To create your own tool, you need to either implement the `Tool` class or use `DynamicTool.` More information is available in the [tool documentation](tools.md).

### Cache

Expand Down
122 changes: 122 additions & 0 deletions docs/tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Tools

## Introduction

Tools in the context of an agent refer to additional functionalities or capabilities integrated with the agent to perform specific tasks beyond text processing.

These tools extend the agent's abilities, allowing it to interact with external systems, access information, and execute actions.

## Development

### Writing a new tool

To create a tool, the `Tool` class must be extended/ implemented or `DynamicTool` created from [tools base](../src/tools/base.ts). When starting to write tools, it is recommended to implement the `Tool` class. The `DynamicTool` class is an extension of the `Tool` class that allows for dynamic forms of input. Refer to the list of [examples](#examples) for some simple examples or any of the built-in tools in order to guide the creation of new tools.

Tools MUST do the following:

- Implement the `Tool` class:

`MyNewToolOutput` is required, must be an implementation of `ToolOutput` such as `StringToolOutput` or `JSONToolOutput`

`ToolOptions` is optional (default BaseToolOptions), constructor parameters that are passed during tool creation

`ToolRunOptions` is optional (default BaseToolRunOptions), optional parameters that are passed to the run method

```typescript
import { BaseToolOptions, BaseToolRunOptions } from "bee-agent-framework/tools/base";

type ToolOptions = BaseToolOptions;
type ToolRunOptions = BaseToolRunOptions;

export class MyNewTool extends Tool<MyNewToolOutput, ToolOptions, ToolRunOptions> {
Tomas2D marked this conversation as resolved.
Show resolved Hide resolved
// tool implementation
}
```

- Be given a unique name:

Note: Convention and best practice is to set the tool's name to the name of its class

```typescript
name = "MyNewTool";
Tomas2D marked this conversation as resolved.
Show resolved Hide resolved
```

- Provide a natural language description of what the tool does:

❗Important: this description is used by the agent to determine when the tool should be used. It's probably the most important aspect of your tool and you should experiment with different natural language descriptions to ensure the tool is used in the correct circumstances.

```typescript
description = "Takes X action when given Y input resulting in Z output";
```

- Declare an input schema:

This is used to define the format of the input to your tool. The agent will formalise the natural language input(s) it has received and structure them into the fields described in the tool's input. The input schema can be specified using [Zod](https://github.com/colinhacks/zod) (recommended) or JSONSchema. It must be a function (either sync or async). Zod effects (e.g. `z.object().transform(...)`) are not supported. The return value of `inputSchema` must always be an object and pass validation by the `validateSchema()` function defined in [schema.ts](../src/internals/helpers/schema.ts).

<!-- eslint-skip -->

```typescript
inputSchema() {
// any Zod definition is good here, this is typical simple example
return z.object({
// list of key-value pairs
});
}
```

- Implement initialisation:

The unnamed static block is executed when your tool is called for the first time. It is used for registering your tool as `serializable` to the agent and any other custom initialisation that may be required.

<!-- eslint-skip -->

```typescript
static {
this.register();
}
```

- Implement the `_run()` method:

<!-- eslint-skip -->

```typescript
protected async _run(input: ToolInput<this>, options?: BaseToolRunOptions) {
Tomas2D marked this conversation as resolved.
Show resolved Hide resolved
// insert custom code here
// MUST: return an instance of the output type specified in the tool class definition
// MAY: throw an instance of ToolError upon unrecoverable error conditions encountered by the tool
}
```

### Using tools with agents

In order for a tool to be of some utility within an agent, you must enable the agent with knowledge of the tool. To do this, the tool code module must be imported into the agent and passed to the tools array during creation of a `BeeAgent`. An example can be found in the [bee agent](../examples/agents/bee.ts) or you can use a code snippet such as the one below that creates an agent with the built-in [ArXiv tool](../src/tools/arxiv.ts):

```typescript
import { ArXivTool } from "bee-agent-framework/tools/arxiv";

const llm = new OllamaChatLLM({
modelId: "insert-model-id-here",
});

const agent = new BeeAgent({
llm,
memory: new TokenMemory({ llm }),
tools: [new ArXivTool()],
});
```

## Examples

### Hello World

The simplest form of an example tool is the [helloworld](../examples/tools/helloWorld.ts) tool. This example implements the Tool class and will simply prepend the word "Hello" to a given input such as "Please give a special greeting to Bee!". It is not intended for usage with any agents since the functionality provided is highly trivial. However, it may serve as a starter template for writing new tools.

### Open Library

The [openlibrary](../examples/tools/openLibrary.ts) tool allows an agent to query the [Open Library](https://openlibrary.org/) via its [book search API](https://openlibrary.org/dev/docs/api/search). This functionality injects knowledge about book metadata (not book content) into an agent. It serves as an example for several key aspects of tool creation:

- Implementing the `Tool` class
- Specifying the input schema to a tool
- Calling out to a third-party service and handling responses and errors
- Using `JSONToolOutput` to return JSON formatted data to the agent
32 changes: 32 additions & 0 deletions examples/tools/helloWorld.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
BaseToolOptions,
BaseToolRunOptions,
StringToolOutput,
Tool,
ToolInput,
} from "@/tools/base.js";
import { z } from "zod";

type ToolOptions = BaseToolOptions;
type ToolRunOptions = BaseToolRunOptions;

export class HelloWorldTool extends Tool<StringToolOutput, ToolOptions, ToolRunOptions> {
name = "HelloWorld";
description = "Says hello when asked for a special greeting.";

inputSchema() {
return z.object({
identifier: z
.string()
.describe("The identifier (person, object, animal, etc.) used to when saying Hello"),
});
}

static {
this.register();
}

protected async _run(input: ToolInput<this>): Promise<StringToolOutput> {
return new StringToolOutput(`Hello, ${input.identifier}`);
}
}
136 changes: 136 additions & 0 deletions examples/tools/openLibrary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {
BaseToolOptions,
BaseToolRunOptions,
Tool,
ToolInput,
JSONToolOutput,
ToolError,
} from "@/tools/base.js";
import { z } from "zod";
import { createURLParams } from "@/internals/fetcher.js";

type ToolOptions = BaseToolOptions;
type ToolRunOptions = BaseToolRunOptions;

export interface OpenLibraryResponse {
numFound: number;
start: number;
numFoundExact: boolean;
num_found: number;
q: string;
offset: number;
docs: {
_version_: number;
key: string;
title: string;
subtitle: string;
alternative_title: string;
alternative_subtitle: string;
cover_i: number;
ebook_access: string;
ebook_count_i: number;
edition_count: number;
edition_key: string[];
format: string[];
publish_date: string[];
lccn: string[];
ia: string[];
oclc: string[];
public_scan_b: boolean;
isbn: string[];
contributor: string[];
publish_place: string[];
publisher: string[];
seed: string[];
first_sentence: string[];
author_key: string[];
author_name: string[];
author_alternative_name: string[];
subject: string[];
person: string[];
place: string[];
time: string[];
has_fulltext: boolean;
title_suggest: string;
title_sort: string;
type: string;
publish_year: number[];
language: string[];
last_modified_i: number;
number_of_pages_median: number;
place_facet: string[];
publisher_facet: string[];
author_facet: string[];
first_publish_year: number;
ratings_count_1: number;
ratings_count_2: number;
ratings_count_3: number;
ratings_count_4: number;
ratings_count_5: number;
ratings_average: number;
ratings_sortable: number;
ratings_count: number;
readinglog_count: number;
want_to_read_count: number;
currently_reading_count: number;
already_read_count: number;
subject_key: string[];
person_key: string[];
place_key: string[];
subject_facet: string[];
time_key: string[];
lcc: string[];
ddc: string[];
lcc_sort: string;
ddc_sort: string;
}[];
}

export class OpenLibraryToolOutput extends JSONToolOutput<OpenLibraryResponse> {
isEmpty(): boolean {
return !this.result || this.result.numFound === 0 || this.result.docs.length === 0;
}
}

export class OpenLibraryTool extends Tool<OpenLibraryToolOutput, ToolOptions, ToolRunOptions> {
name = "OpenLibrary";
description =
"Provides access to a library of books with information about book titles, authors, contributors, publication dates, publisher and isbn.";

inputSchema() {
return z
.object({
title: z.string(),
author: z.string(),
isbn: z.string(),
subject: z.string(),
place: z.string(),
person: z.string(),
publisher: z.string(),
})
.partial();
}

static {
this.register();
}

protected async _run(input: ToolInput<this>, options?: ToolRunOptions) {
const params = createURLParams(input);
const url = `https://openlibrary.org/search.json?${decodeURIComponent(params.toString())}`;
const response = await fetch(url, {
signal: options?.signal,
});
if (!response.ok) {
throw new ToolError("Request to Open Library API has failed!", [
new Error(await response.text()),
]);
}
try {
const json = await response.json();
return new OpenLibraryToolOutput(json);
} catch (e) {
throw new ToolError("Request to Open Library has failed to parse!", [e]);
}
}
}