Agentic tools that support the Bee Agent Framework
20 |
21 |
22 | The tools in this repository are additional to those provided within the core bee-agent-framework. They provide access to various functions that enable agents to connect to a variety of different capabilities. More information about developing tools for Bee can be found in the [tools documentation](https://github.com/i-am-bee/bee-agent-framework/blob/main/docs/tools.md).
23 |
24 | ### 🛠️ Tools
25 |
26 | | Name | Description |
27 | | ----------------- | ------------------------------------------------------- |
28 | | Hello World | Trivial example tool |
29 | | Image Description | Use an LLM to get a text description for an image |
30 | | Open Library | Connect to the Open Library for information about books |
31 | | Airtable | Query the tables within an airtable base |
32 |
33 | ➕ [Request](https://github.com/i-am-bee/bee-community-tools/discussions)
34 |
35 | ## Getting started with Bee Community Tools
36 |
37 | ### Installation
38 |
39 | ```shell
40 | yarn install
41 | ```
42 |
43 | ### Run an example agent with tools
44 |
45 | We provide example agents for tool usage in `examples/agents/` that you can use to test tools.
46 |
47 | ```shell
48 | yarn start
49 | ```
50 |
51 | The `allToolsAgent` example agent is configured to use a BAM, Watsonx, OpenAI hosted LLM, or a local Ollama LLM.
52 | If you are using a hosted LLM make sure to create .env (from .env.template) and fill in the necessary API_KEY.
53 |
54 | > [!NOTE]
55 | > The Hello World example tool is not enabled by default.
56 |
57 | > [!TIP]
58 | > Tools can be enabled/disabled in `examples/agents/allToolsAgent.ts`
59 |
60 | ## Contribution guidelines
61 |
62 | Bee Community Tools is an open-source project and we ❤️ contributions.
63 |
64 | If you'd like to contribute to an existing tool or create a new one, please take a look at our [contribution guidelines](./CONTRIBUTING.md).
65 |
66 | ### 🐛 Bugs
67 |
68 | We are using [GitHub Issues](https://github.com/i-am-bee/bee-community-tools/issues) to manage our public bugs. We keep a close eye on this, so before filing a new issue, please check to make sure it hasn't already been logged.
69 |
70 | ### 🗒 Code of conduct
71 |
72 | This project and everyone participating in it are governed by the [Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please read the [full text](./CODE_OF_CONDUCT.md) so that you can read which actions may or may not be tolerated.
73 |
74 | ## 🗒 Legal notice
75 |
76 | All content in these repositories including code has been provided by IBM under the associated open source software license and IBM is under no obligation to provide enhancements, updates, or support. IBM developers produced this code as an open source project (not as an IBM product), and IBM makes no assertions as to the level of quality nor security, and will not be maintaining this code going forward.
77 |
78 | ## Contributors
79 |
80 | Special thanks to our contributors for helping us improve Bee Community Tools.
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/src/tools/imageDescription.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2024 IBM Corp.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {
18 | BaseToolOptions,
19 | BaseToolRunOptions,
20 | StringToolOutput,
21 | ToolInput,
22 | ToolError,
23 | Tool,
24 | } from "bee-agent-framework/tools/base";
25 | import { getEnv, parseEnv } from "bee-agent-framework/internals/env";
26 | import { z } from "zod";
27 |
28 | type ToolOptions = BaseToolOptions;
29 | type ToolRunOptions = BaseToolRunOptions;
30 |
31 | export interface VllmChatCompletionImageURL {
32 | url: string;
33 | }
34 |
35 | export interface VllmChatCompletionContent {
36 | type: string;
37 | text?: string;
38 | image_url?: VllmChatCompletionImageURL;
39 | }
40 | export interface VllmChatCompletionMessage {
41 | role: string;
42 | content: VllmChatCompletionContent[];
43 | }
44 |
45 | export interface VllmChatCompletionPrompt {
46 | model: string;
47 | messages: VllmChatCompletionMessage[];
48 | }
49 |
50 | export class ImageDescriptionTool extends Tool {
51 | name = "ImageDescription";
52 | description = "Describes the content of an image provided.";
53 |
54 | protected vllmApiEndpoint = parseEnv("IMAGE_DESC_VLLM_API", z.string().url());
55 | protected vllmApiModelId = parseEnv("IMAGE_DESC_MODEL_ID", z.string());
56 | protected openApiKey = getEnv("OPENAI_API_KEY");
57 |
58 | inputSchema() {
59 | return z.object({
60 | imageUrl: z.string().describe("The URL of an image."),
61 | prompt: z.string().optional().describe("Image specific prompt from the user."),
62 | });
63 | }
64 |
65 | static {
66 | this.register();
67 | }
68 |
69 | protected async _run(
70 | { prompt = "Describe this image.", imageUrl }: ToolInput,
71 | _options?: BaseToolRunOptions,
72 | ): Promise {
73 | const imageDescriptionOutput = await this.requestImageDescriptionForURL(
74 | imageUrl,
75 | prompt,
76 | _options?.signal,
77 | );
78 |
79 | return new StringToolOutput(
80 | `Description: ${imageDescriptionOutput}. Ignore any misleading information that may be in the URL.`,
81 | );
82 | }
83 |
84 | createSnapshot() {
85 | return {
86 | ...super.createSnapshot(),
87 | vllmApiEndpoint: this.vllmApiEndpoint,
88 | vllmApiModelId: this.vllmApiModelId,
89 | openApiKey: this.openApiKey, // pragma: allowlist secret
90 | };
91 | }
92 |
93 | loadSnapshot({
94 | vllmApiEndpoint,
95 | vllmApiModelId,
96 | openApiKey,
97 | ...snapshot
98 | }: ReturnType) {
99 | super.loadSnapshot(snapshot);
100 | Object.assign(this, {
101 | vllmApiEndpoint,
102 | vllmApiModelId,
103 | openApiKey,
104 | });
105 | }
106 |
107 | protected async queryVllmAPI(completionPrompt: VllmChatCompletionPrompt, signal?: AbortSignal) {
108 | const vllmApiUrl = new URL("/v1/chat/completions", this.vllmApiEndpoint);
109 | const headers = {
110 | "accept": "application/json",
111 | "Content-Type": "application/json",
112 | };
113 | if (this.openApiKey !== undefined) {
114 | Object.assign(headers, { Authorization: `Bearer ${this.openApiKey}` });
115 | }
116 | const vllmResponse = await fetch(vllmApiUrl, {
117 | method: "POST",
118 | body: JSON.stringify(completionPrompt),
119 | headers: headers,
120 | signal: signal,
121 | });
122 |
123 | if (!vllmResponse.ok) {
124 | throw new ToolError(`Request to Vllm API has failed! ${vllmResponse.statusText}`, [
125 | new Error(await vllmResponse.text()),
126 | ]);
127 | }
128 | try {
129 | const json = await vllmResponse.json();
130 | if (json.choices.length > 0) {
131 | // We have an answer
132 | const content = json.choices[0].message.content;
133 | return content;
134 | } else {
135 | return "The model could not identify the image.";
136 | }
137 | } catch (e) {
138 | throw new ToolError(`Request to Vllm has failed to parse! ${e}`, [e]);
139 | }
140 | }
141 |
142 | /**
143 | * Requests the description of an image from the vLLM Backend
144 | *
145 | * @param imageUrl - The Image Url
146 | * @param prompt - The prompt to provide to the model alongside the image
147 | *
148 | * @returns A String description of the image.
149 | */
150 | protected async requestImageDescriptionForURL(
151 | imageUrl: string,
152 | prompt: string,
153 | signal?: AbortSignal,
154 | ): Promise {
155 | const modelPrompt: VllmChatCompletionPrompt = {
156 | model: this.vllmApiModelId,
157 | messages: [
158 | {
159 | role: "user",
160 | content: [
161 | { type: "text", text: prompt },
162 | { type: "image_url", image_url: { url: imageUrl } },
163 | ],
164 | },
165 | ],
166 | };
167 |
168 | const modelResponse = await this.queryVllmAPI(modelPrompt, signal);
169 | return modelResponse;
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Bee Agent Framework is an open-source project committed to bringing LLM agents to people of all backgrounds. This page describes how you can join the Bee community in this goal.
4 |
5 | ## Before you start
6 |
7 | If you are new to Bee contributing, we recommend you do the following before diving into the code:
8 |
9 | - Read [Bee Overview](https://github.com/i-am-bee/bee-agent-framework/blob/main/docs/overview.md) to understand core concepts.
10 | - Read [Code of Conduct](./CODE_OF_CONDUCT.md).
11 |
12 | ## Choose an issue to work on
13 |
14 | Bee uses the following labels to help non-maintainers find issues best suited to their interests and experience level:
15 |
16 | - [good first issue](https://github.com/i-am-bee/bee-community-tools/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) - these issues are typically the simplest available to work on, ideal for newcomers. They should already be fully scoped, with a straightforward approach outlined in the descriptions.
17 | - [help wanted](https://github.com/i-am-bee/bee-community-tools/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) - these issues are generally more complex than good first issues. They typically cover work that core maintainers don't currently have the capacity to implement and may require more investigation/discussion. These are great options for experienced contributors looking for something more challenging.
18 |
19 | ## Set up a development environment
20 |
21 | To start contributing tooling to the Bee Agent framework, follow these steps to set up your development environment:
22 |
23 | 1. **Install a Node Version Manager (NVM or n):** We use `.nvmrc` to specify the required Node.js version. Install [nvm](https://github.com/nvm-sh/nvm) or or [n](https://github.com/tj/n) by following their official installation instructions.
24 |
25 | 1. **Install the Correct Node.js Version:** Use `nvm` to install and use the Node.js version specified in the `.nvmrc` file:
26 |
27 | ```bash
28 | nvm install && nvm use || n auto
29 | ```
30 |
31 | 1. **Install IBM Detect Secrets:** We use [IBM Detect Secrets](https://github.com/IBM/detect-secrets) to help ensure that secrets are not leaked into the code base. This tool is run as part a pre-commit hook so will be required before you can make commits.
32 |
33 | 1. **Install [Yarn](https://yarnpkg.com/) via Corepack:** This project uses Yarn as the package manager. Ensure you have Corepack enabled and install Yarn:
34 |
35 | ```bash
36 | corepack enable
37 | ```
38 |
39 | 1. **Install Dependencies:** Install all project dependencies by running:
40 |
41 | ```bash
42 | yarn install
43 | ```
44 |
45 | 1. **Setup environmental variables:** See the [.env.template](.env.template) file for an example and fill out the appropriate values
46 |
47 | ```bash
48 | cp .env.template .env
49 | ```
50 |
51 | 1. **Follow Conventional Commit Messages:** We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) to structure our commit messages. This helps maintain a clean and manageable commit history. Please use the following format:
52 |
53 | ```
54 | ():
55 | Type: feat, fix, chore, docs, style, refactor, perf, test, etc.
56 | Scope: The area of the codebase your changes affect (optional).
57 | Subject: A short description of the changes (required).
58 | ```
59 |
60 | _Example:_
61 |
62 | ```
63 | feat(watsonx): add llm streaming support
64 |
65 | Ref: #15
66 | ```
67 |
68 | 1. **Run Linters/Formatters:** Ensure your changes meet code quality standards. Run the following commands:
69 |
70 | ```shell
71 | yarn lint # or yarn lint:fix
72 | yarn format # or yarn lint:format
73 | ```
74 |
75 | 1. **Run Tests:** Ensure your changes pass all tests (unit, integration, E2E). Run the following commands:
76 |
77 | ```shell
78 | yarn test:unit
79 | yarn test:e2e
80 | ```
81 |
82 | By following the above steps, you'll be all set to contribute to our project! If you encounter any issues during the setup process, please feel free to open an issue.
83 |
84 | ## Style and lint
85 |
86 | Bee uses the following tools to meet code quality standards and ensure a unified code style across the codebase.
87 |
88 | - [ESLint](https://eslint.org/) - Linting Utility
89 | - [Prettier](https://prettier.io/) - Code Formatter
90 | - [commitlint](https://commitlint.js.org/) - Lint commit messages according to [Conventional Commits](https://www.conventionalcommits.org/).
91 |
92 | ## Issues and pull requests
93 |
94 | We use GitHub pull requests to accept contributions.
95 |
96 | While not required, opening a new issue about the bug you're fixing or the feature you're working on before you open a pull request is important in starting a discussion with the community about your work. The issue gives us a place to talk about the idea and how we can work together to implement it in the code. It also lets the community know what you're working on, and if you need help, you can reference the issue when discussing it with other community and team members.
97 |
98 | If you've written some code but need help finishing it, want to get initial feedback on it before finishing it, or want to share it and discuss it prior to completing the implementation, you can open a Draft pull request and prepend the title with the [WIP] tag (for Work In Progress). This will indicate to reviewers that the code in the PR isn't in its final state and will change. It also means we will only merge the commit once it is finished. You or a reviewer can remove the [WIP] tag when the code is ready to be thoroughly reviewed for merging.
99 |
100 | ## Contributor Licensing Agreement
101 |
102 | Before you can submit any code, all contributors must sign a contributor license agreement (CLA). By signing a CLA, you're attesting that you are the author of the contribution, and that you're freely contributing it under the terms of the Apache-2.0 license.
103 |
--------------------------------------------------------------------------------
/src/tools/openLibrary.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2024 IBM Corp.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {
18 | BaseToolOptions,
19 | BaseToolRunOptions,
20 | Tool,
21 | ToolInput,
22 | JSONToolOutput,
23 | ToolError,
24 | } from "bee-agent-framework/tools/base";
25 | import { z } from "zod";
26 | import { createURLParams } from "bee-agent-framework/internals/fetcher";
27 |
28 | type ToolOptions = BaseToolOptions;
29 | type ToolRunOptions = BaseToolRunOptions;
30 |
31 | export interface OpenLibraryAPIResponse {
32 | numFound: number;
33 | start: number;
34 | numFoundExact: boolean;
35 | num_found: number;
36 | q: string;
37 | offset: number;
38 | docs: {
39 | _version_: number;
40 | key: string;
41 | title: string;
42 | subtitle: string;
43 | alternative_title: string;
44 | alternative_subtitle: string;
45 | cover_i: number;
46 | ebook_access: string;
47 | ebook_count_i: number;
48 | edition_count: number;
49 | edition_key: string[];
50 | format: string[];
51 | publish_date: string[];
52 | lccn: string[];
53 | ia: string[];
54 | oclc: string[];
55 | public_scan_b: boolean;
56 | isbn: string[];
57 | contributor: string[];
58 | publish_place: string[];
59 | publisher: string[];
60 | seed: string[];
61 | first_sentence: string[];
62 | author_key: string[];
63 | author_name: string[];
64 | author_alternative_name: string[];
65 | subject: string[];
66 | person: string[];
67 | place: string[];
68 | time: string[];
69 | has_fulltext: boolean;
70 | title_suggest: string;
71 | title_sort: string;
72 | type: string;
73 | publish_year: number[];
74 | language: string[];
75 | last_modified_i: number;
76 | number_of_pages_median: number;
77 | place_facet: string[];
78 | publisher_facet: string[];
79 | author_facet: string[];
80 | first_publish_year: number;
81 | ratings_count_1: number;
82 | ratings_count_2: number;
83 | ratings_count_3: number;
84 | ratings_count_4: number;
85 | ratings_count_5: number;
86 | ratings_average: number;
87 | ratings_sortable: number;
88 | ratings_count: number;
89 | readinglog_count: number;
90 | want_to_read_count: number;
91 | currently_reading_count: number;
92 | already_read_count: number;
93 | subject_key: string[];
94 | person_key: string[];
95 | place_key: string[];
96 | subject_facet: string[];
97 | time_key: string[];
98 | lcc: string[];
99 | ddc: string[];
100 | lcc_sort: string;
101 | ddc_sort: string;
102 | }[];
103 | }
104 |
105 | interface OpenLibraryResponse {
106 | title: string;
107 | author_name: string[];
108 | contributor: string[];
109 | first_publish_year: number;
110 | publish_date: string[];
111 | language: string[];
112 | publish_place: string[];
113 | place: string[];
114 | publisher: string[];
115 | isbn: string[];
116 | }
117 |
118 | export interface OpenLibraryResponseList extends Array {}
119 |
120 | export class OpenLibraryToolOutput extends JSONToolOutput {}
121 |
122 | export class OpenLibraryTool extends Tool {
123 | name = "OpenLibrary";
124 | description =
125 | "Provides access to a library of books with information about book titles, authors, contributors, publication dates, publisher and isbn.";
126 |
127 | inputSchema() {
128 | return z
129 | .object({
130 | title: z.string().describe("The title or name of the book."),
131 | author: z.string().describe("The author's name."),
132 | isbn: z.string().describe("The book's International Standard Book Number (ISBN)."),
133 | subject: z.string().describe("The subject or topic about which the book is written."),
134 | place: z.string().describe("The place about which the book is written."),
135 | person: z.string().describe("The person about which the book is written."),
136 | publisher: z.string().describe("The company or name of the book's publisher."),
137 | })
138 | .partial();
139 | }
140 |
141 | static {
142 | this.register();
143 | }
144 |
145 | protected async _run(input: ToolInput, options?: ToolRunOptions) {
146 | const params = createURLParams(input);
147 | const url = `https://openlibrary.org/search.json?${decodeURIComponent(params.toString())}`;
148 | const response = await fetch(url, {
149 | signal: options?.signal,
150 | });
151 | if (!response.ok) {
152 | throw new ToolError("Request to Open Library API has failed!", [
153 | new Error(await response.text()),
154 | ]);
155 | }
156 | try {
157 | const responseJson: OpenLibraryAPIResponse = await response.json();
158 | const json: OpenLibraryResponseList = responseJson.docs.map((doc) => {
159 | return {
160 | title: doc.title || "Unknown",
161 | author_name: doc.author_name || [],
162 | contributor: doc.contributor || [],
163 | first_publish_year: doc.first_publish_year || 0,
164 | publish_date: doc.publish_date || [],
165 | language: doc.language || [],
166 | publish_place: doc.publish_place || [],
167 | place: doc.place || [],
168 | publisher: doc.publisher || [],
169 | isbn: doc.isbn || [],
170 | };
171 | });
172 | return new OpenLibraryToolOutput(json);
173 | } catch (e) {
174 | throw new ToolError("Request to Open Library has failed to parse!", [e]);
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/examples/agents/airTableToolAgent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2024 IBM Corp.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import "dotenv/config.js";
18 | import { BAMChatLLM } from "bee-agent-framework/adapters/bam/chat";
19 | import { BeeAgent } from "bee-agent-framework/agents/bee/agent";
20 | import { createConsoleReader } from "../helpers/io.js";
21 | import { FrameworkError } from "bee-agent-framework/errors";
22 | import { TokenMemory } from "bee-agent-framework/memory/tokenMemory";
23 | import { Logger } from "bee-agent-framework/logger/logger";
24 | import { OllamaChatLLM } from "bee-agent-framework/adapters/ollama/chat";
25 | import { OpenAIChatLLM } from "bee-agent-framework/adapters/openai/chat";
26 | import { PromptTemplate } from "bee-agent-framework/template";
27 | import { WatsonXChatLLM } from "bee-agent-framework/adapters/watsonx/chat";
28 | import { parseEnv } from "bee-agent-framework/internals/env";
29 | import { z } from "zod";
30 | import {
31 | BeeSystemPrompt,
32 | BeeToolErrorPrompt,
33 | BeeToolInputErrorPrompt,
34 | BeeToolNoResultsPrompt,
35 | } from "bee-agent-framework/agents/bee/prompts";
36 |
37 | // core tools
38 | import { DuckDuckGoSearchTool } from "bee-agent-framework/tools/search/duckDuckGoSearch";
39 | import { WikipediaTool } from "bee-agent-framework/tools/search/wikipedia";
40 |
41 | // AirTable tool
42 | import { AirtableTool } from "bee-community-tools/tools/airtable";
43 |
44 | const AIRTABLE_TOKEN: string = parseEnv("AIRTABLE_TOKEN", z.string());
45 | const AIRTABLE_BASE: string = parseEnv("AIRTABLE_BASE", z.string());
46 |
47 | Logger.root.level = "silent"; // disable internal logs
48 | const logger = new Logger({ name: "app", level: "trace" });
49 |
50 | async function runBeeAgent() {
51 | // use BAM if GENAI_API_KEY env var is defined
52 | // else use Watsonx if WATSONX_API_KEY and WATSONX_PROJECT_ID env vars are defined
53 | // else use OpenAI if OPENAI_API_KEY env var is defined
54 | // else use Ollama
55 | const llm =
56 | process.env.GENAI_API_KEY !== undefined
57 | ? BAMChatLLM.fromPreset("meta-llama/llama-3-1-70b-instruct")
58 | : process.env.WATSONX_API_KEY !== undefined && process.env.WATSONX_PROJECT_ID !== undefined
59 | ? WatsonXChatLLM.fromPreset("meta-llama/llama-3-1-70b-instruct", {
60 | apiKey: process.env.WATSONX_API_KEY, //pragma: allowlist secret
61 | projectId: process.env.WATSONX_PROJECT_ID,
62 | parameters: {
63 | decoding_method: "greedy",
64 | min_new_tokens: 5,
65 | max_new_tokens: 50,
66 | },
67 | })
68 | : process.env.OPENAI_API_KEY !== undefined
69 | ? new OpenAIChatLLM()
70 | : new OllamaChatLLM({ modelId: "llama3.1" });
71 |
72 | const agent = new BeeAgent({
73 | llm,
74 | memory: new TokenMemory({ llm }),
75 | tools: [
76 | new DuckDuckGoSearchTool(),
77 | new WikipediaTool(),
78 | new AirtableTool({ apiToken: AIRTABLE_TOKEN, baseId: AIRTABLE_BASE }),
79 | ],
80 | templates: {
81 | user: new PromptTemplate({
82 | schema: z
83 | .object({
84 | input: z.string(),
85 | })
86 | .passthrough(),
87 | template: `User: {{input}}`,
88 | }),
89 | system: BeeSystemPrompt.fork((old) => ({
90 | ...old,
91 | defaults: {
92 | instructions: "You are a helpful assistant that uses tools to answer questions.",
93 | },
94 | })),
95 | toolError: BeeToolErrorPrompt,
96 | toolInputError: BeeToolInputErrorPrompt,
97 | toolNoResultError: BeeToolNoResultsPrompt.fork((old) => ({
98 | ...old,
99 | template: `${old.template}\nPlease reformat your input.`,
100 | })),
101 | toolNotFoundError: new PromptTemplate({
102 | schema: z
103 | .object({
104 | tools: z.array(z.object({ name: z.string() }).passthrough()),
105 | })
106 | .passthrough(),
107 | template: `Tool does not exist!
108 | {{#tools.length}}
109 | Use one of the following tools: {{#trim}}{{#tools}}{{name}},{{/tools}}{{/trim}}
110 | {{/tools.length}}`,
111 | }),
112 | },
113 | });
114 |
115 | const reader = createConsoleReader();
116 |
117 | try {
118 | for await (const { prompt } of reader) {
119 | const response = await agent
120 | .run(
121 | { prompt },
122 | {
123 | execution: {
124 | maxRetriesPerStep: 3,
125 | totalMaxRetries: 10,
126 | maxIterations: 20,
127 | },
128 | signal: AbortSignal.timeout(2 * 60 * 1000),
129 | },
130 | )
131 | .observe((emitter) => {
132 | emitter.on("start", () => {
133 | reader.write(`Agent 🤖 : `, "Starting new iteration");
134 | });
135 | emitter.on("error", ({ error }) => {
136 | reader.write(`Agent 🤖 : `, FrameworkError.ensure(error).dump());
137 | });
138 | emitter.on("retry", () => {
139 | reader.write(`Agent 🤖 : `, "retrying the action...");
140 | });
141 | emitter.on("update", async ({ update }) => {
142 | // log 'data' to see the whole state
143 | // to log only valid runs (no errors), check if meta.success === true
144 | reader.write(`Agent (${update.key}) 🤖 : `, update.value);
145 | });
146 | });
147 |
148 | reader.write(`Agent 🤖 : `, response.result.text);
149 | }
150 | } catch (error) {
151 | logger.error(FrameworkError.ensure(error).dump());
152 | } finally {
153 | process.exit(0);
154 | }
155 | }
156 |
157 | void runBeeAgent();
158 |
--------------------------------------------------------------------------------
/examples/agents/manyToolsAgent.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2024 IBM Corp.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import "dotenv/config.js";
18 | import { BAMChatLLM } from "bee-agent-framework/adapters/bam/chat";
19 | import { BeeAgent } from "bee-agent-framework/agents/bee/agent";
20 | import { createConsoleReader } from "../helpers/io.js";
21 | import { FrameworkError } from "bee-agent-framework/errors";
22 | import { TokenMemory } from "bee-agent-framework/memory/tokenMemory";
23 | import { Logger } from "bee-agent-framework/logger/logger";
24 | import { OllamaChatLLM } from "bee-agent-framework/adapters/ollama/chat";
25 | import { OpenAIChatLLM } from "bee-agent-framework/adapters/openai/chat";
26 | import { PromptTemplate } from "bee-agent-framework/template";
27 | import { WatsonXChatLLM } from "bee-agent-framework/adapters/watsonx/chat";
28 | import { z } from "zod";
29 | import {
30 | BeeSystemPrompt,
31 | BeeToolErrorPrompt,
32 | BeeToolInputErrorPrompt,
33 | BeeToolNoResultsPrompt,
34 | } from "bee-agent-framework/agents/bee/prompts";
35 |
36 | // core tools
37 | import { DuckDuckGoSearchTool } from "bee-agent-framework/tools/search/duckDuckGoSearch";
38 | import { WikipediaTool } from "bee-agent-framework/tools/search/wikipedia";
39 | // import { OpenMeteoTool } from "bee-agent-framework/tools/weather/openMeteo";
40 | // import { ArXivTool } from "bee-agent-framework/tools/arxiv";
41 |
42 | // contrib tools
43 | // import { HelloWorldTool } from "@/tools/helloWorld.js";
44 | import { OpenLibraryTool } from "bee-community-tools/tools/openLibrary";
45 | import { ImageDescriptionTool } from "bee-community-tools/tools/imageDescription";
46 |
47 | Logger.root.level = "silent"; // disable internal logs
48 | const logger = new Logger({ name: "app", level: "trace" });
49 |
50 | async function runBeeAgent() {
51 | // use BAM if GENAI_API_KEY env var is defined
52 | // else use Watsonx if WATSONX_API_KEY and WATSONX_PROJECT_ID env vars are defined
53 | // else use OpenAI if OPENAI_API_KEY env var is defined
54 | // else use Ollama
55 | const llm =
56 | process.env.GENAI_API_KEY !== undefined
57 | ? BAMChatLLM.fromPreset("meta-llama/llama-3-1-70b-instruct")
58 | : process.env.WATSONX_API_KEY !== undefined && process.env.WATSONX_PROJECT_ID !== undefined
59 | ? WatsonXChatLLM.fromPreset("meta-llama/llama-3-1-70b-instruct", {
60 | apiKey: process.env.WATSONX_API_KEY, //pragma: allowlist secret
61 | projectId: process.env.WATSONX_PROJECT_ID,
62 | parameters: {
63 | decoding_method: "greedy",
64 | min_new_tokens: 5,
65 | max_new_tokens: 50,
66 | },
67 | })
68 | : process.env.OPENAI_API_KEY !== undefined
69 | ? new OpenAIChatLLM()
70 | : new OllamaChatLLM({ modelId: "llama3.1" });
71 |
72 | const agent = new BeeAgent({
73 | llm,
74 | memory: new TokenMemory({ llm }),
75 | tools: [
76 | new DuckDuckGoSearchTool(),
77 | new WikipediaTool(),
78 | // new OpenMeteoTool(),
79 | // new ArXivTool(),
80 | // new HelloWorldTool(),
81 | new OpenLibraryTool(),
82 | new ImageDescriptionTool(),
83 | ],
84 | templates: {
85 | user: new PromptTemplate({
86 | schema: z
87 | .object({
88 | input: z.string(),
89 | })
90 | .passthrough(),
91 | template: `User: {{input}}`,
92 | }),
93 | system: BeeSystemPrompt.fork((old) => ({
94 | ...old,
95 | defaults: {
96 | instructions: "You are a helpful assistant that uses tools to answer questions.",
97 | },
98 | })),
99 | toolError: BeeToolErrorPrompt,
100 | toolInputError: BeeToolInputErrorPrompt,
101 | toolNoResultError: BeeToolNoResultsPrompt.fork((old) => ({
102 | ...old,
103 | template: `${old.template}\nPlease reformat your input.`,
104 | })),
105 | toolNotFoundError: new PromptTemplate({
106 | schema: z
107 | .object({
108 | tools: z.array(z.object({ name: z.string() }).passthrough()),
109 | })
110 | .passthrough(),
111 | template: `Tool does not exist!
112 | {{#tools.length}}
113 | Use one of the following tools: {{#trim}}{{#tools}}{{name}},{{/tools}}{{/trim}}
114 | {{/tools.length}}`,
115 | }),
116 | },
117 | });
118 |
119 | const reader = createConsoleReader();
120 |
121 | try {
122 | for await (const { prompt } of reader) {
123 | const response = await agent
124 | .run(
125 | { prompt },
126 | {
127 | execution: {
128 | maxRetriesPerStep: 3,
129 | totalMaxRetries: 10,
130 | maxIterations: 20,
131 | },
132 | signal: AbortSignal.timeout(2 * 60 * 1000),
133 | },
134 | )
135 | .observe((emitter) => {
136 | emitter.on("start", () => {
137 | reader.write(`Agent 🤖 : `, "Starting new iteration");
138 | });
139 | emitter.on("error", ({ error }) => {
140 | reader.write(`Agent 🤖 : `, FrameworkError.ensure(error).dump());
141 | });
142 | emitter.on("retry", () => {
143 | reader.write(`Agent 🤖 : `, "retrying the action...");
144 | });
145 | emitter.on("update", async ({ update }) => {
146 | // log 'data' to see the whole state
147 | // to log only valid runs (no errors), check if meta.success === true
148 | reader.write(`Agent (${update.key}) 🤖 : `, update.value);
149 | });
150 | });
151 |
152 | reader.write(`Agent 🤖 : `, response.result.text);
153 | }
154 | } catch (error) {
155 | logger.error(FrameworkError.ensure(error).dump());
156 | } finally {
157 | process.exit(0);
158 | }
159 | }
160 |
161 | void runBeeAgent();
162 |
--------------------------------------------------------------------------------
/src/tools/airtable.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2024 IBM Corp.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import {
18 | BaseToolOptions,
19 | BaseToolRunOptions,
20 | JSONToolOutput,
21 | ToolInput,
22 | Tool,
23 | ToolError,
24 | } from "bee-agent-framework/tools/base";
25 | import { z } from "zod";
26 | import Airtable, { FieldSet, Records, SelectOptions } from "airtable";
27 |
28 | type ToolRunOptions = BaseToolRunOptions;
29 |
30 | export interface SummarisedAirtableFieldSchema {
31 | name: string;
32 | type?: string;
33 | }
34 |
35 | export interface SummarisedAirtableTableSchema {
36 | name: string;
37 | id: string;
38 | description?: string;
39 | fields: SummarisedAirtableFieldSchema[];
40 | }
41 |
42 | export interface AirtableToolOptions extends BaseToolOptions {
43 | apiToken: string;
44 | baseId: string;
45 | }
46 |
47 | /**
48 | * Airtable tool for the bee-agent-framework.
49 | *
50 | * This tool allows the agent to retrieve information from the tables within an airtbale base.
51 | * The agent core model will need to be able to construct Airtable Filter formulas in order to
52 | * filter the data in the requested table.
53 | * See: (https://support.airtable.com/docs/airtable-web-api-using-filterbyformula-or-sort-parameters)
54 | *
55 | * The data is retuned in JSON form to the agent as it would be from the API.
56 | */
57 | export class AirtableTool extends Tool, AirtableToolOptions, ToolRunOptions> {
58 | public readonly airtable: Airtable;
59 | public readonly base: Airtable.Base;
60 | airtableApi = "https://api.airtable.com";
61 |
62 | name = "Airtable";
63 | description =
64 | "Can Query records from airtable tables in an airtable base. Use the action GET_SCHEMA to learn about the structure of the airtable base and QUERY_TABLE to request data from a table within the base.";
65 |
66 | inputSchema() {
67 | return z.object({
68 | action: z
69 | .enum(["GET_SCHEMA", "QUERY_TABLE"])
70 | .describe(
71 | "The action for the tool to take. GET_SCHEMA requests the airtable base table schema. QUERY_TABLE requests data from a selected table.",
72 | ),
73 | table: z
74 | .string()
75 | .optional()
76 | .describe("The table ID to query when using the QUERY_TABLE action."),
77 | fields: z
78 | .array(z.string())
79 | .optional()
80 | .describe("The fields to return from the table query when using the QUERY_TABLE action."),
81 | filterFormula: z
82 | .string()
83 | .optional()
84 | .describe(
85 | "The airtable filter formula to refine the query when using the QUERY_TABLE action.",
86 | ),
87 | });
88 | }
89 |
90 | static {
91 | this.register();
92 | }
93 |
94 | public constructor(public readonly options: AirtableToolOptions) {
95 | super(options);
96 | this.airtable = new Airtable({ endpointUrl: this.airtableApi, apiKey: options.apiToken }); // pragma: allowlist secret
97 | this.base = this.airtable.base(options.baseId);
98 | }
99 |
100 | protected async _run(input: ToolInput, _options?: BaseToolRunOptions) {
101 | if (input.action === "GET_SCHEMA") {
102 | const response = await this.getBaseTableSchema(_options?.signal);
103 | return new JSONToolOutput(response);
104 | } else if (input.action === "QUERY_TABLE" && input.table != undefined) {
105 | const response = await this.getTableContents(input.table, input.fields, input.filterFormula);
106 | return new JSONToolOutput(response);
107 | } else {
108 | throw new ToolError("Invalid Action.");
109 | }
110 | }
111 |
112 | /**
113 | * Requests the Airtable Base Schema.
114 | *
115 | * The airtable module does not have this functionality, the tool requests directly
116 | * from the HTTP API.
117 | * @returns - SummarisedAirtableTableSchema
118 | */
119 | private async getBaseTableSchema(signal?: AbortSignal): Promise {
120 | const atResponse = await fetch(
121 | `${this.airtableApi}/v0/meta/bases/${this.options.baseId}/tables`,
122 | {
123 | headers: {
124 | Authorization: `Bearer ${this.options.apiToken}`,
125 | },
126 | signal: signal,
127 | },
128 | );
129 |
130 | if (atResponse.ok) {
131 | const schemadata = await atResponse.json();
132 | const tableSchema: SummarisedAirtableTableSchema[] = schemadata.tables.map(
133 | (table: { name: any; id: any; description: any; fields: any[] }) => ({
134 | name: table.name,
135 | id: table.id,
136 | description: table.description,
137 | fields: table.fields.map((field: SummarisedAirtableFieldSchema) => ({
138 | name: field.name,
139 | type: field.type,
140 | })),
141 | }),
142 | );
143 |
144 | return tableSchema;
145 | } else {
146 | throw new ToolError(`Error occured getting airtable base schema: ${atResponse.text()}`);
147 | }
148 | }
149 |
150 | /**
151 | * Function to request rows from an airtable table using a filter generated by the agent.
152 | *
153 | * Currently this returns ALL rows that the filterFormula matches because it is assumed
154 | * that the agent will need all data available and there is no easy way to allow the agent
155 | * to iterate through pages.
156 | *
157 | * @param tableId - The ID or name of the table
158 | * @param fields - An array of the fields to return
159 | * @param filterFormula - The filterformula to use
160 | * @returns
161 | */
162 | private getTableContents(
163 | tableId: string,
164 | fields?: string[],
165 | filterFormula?: string,
166 | ): Promise> {
167 | const selectOpts: SelectOptions