Skip to content

Commit

Permalink
Merge branch 'main' into add_llm_tool
Browse files Browse the repository at this point in the history
  • Loading branch information
xjacka authored Dec 17, 2024
2 parents 57e6369 + e899308 commit f5ca92d
Show file tree
Hide file tree
Showing 16 changed files with 367 additions and 28 deletions.
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Changelog

## [0.0.20](https://github.com/i-am-bee/bee-api/compare/v0.0.19...v0.0.20) (2024-12-16)

### Bug Fixes

* **artifact:** update the share token only when the share property changes ([#139](https://github.com/i-am-bee/bee-api/issues/139)) ([c808d39](https://github.com/i-am-bee/bee-api/commit/c808d39b3f8c3af6ef206d553512ab7183fd440d))

## [0.0.19](https://github.com/i-am-bee/bee-api/compare/v0.0.18...v0.0.19) (2024-12-16)

### Features

* **user:** update default assistant for new users ([#138](https://github.com/i-am-bee/bee-api/issues/138)) ([233c138](https://github.com/i-am-bee/bee-api/commit/233c138f3075803bffeb127c1956c2742ee98ea5))

## [0.0.18](https://github.com/i-am-bee/bee-api/compare/v0.0.17...v0.0.18) (2024-12-15)

### Features

* **user:** add default assistant when user created ([#137](https://github.com/i-am-bee/bee-api/issues/137)) ([328c8de](https://github.com/i-am-bee/bee-api/commit/328c8ded996db1d8d8d649fbcff24c67f5cee2ed))

### Bug Fixes

* **limits:** merge window and rate limits, fix streaming headers ([#134](https://github.com/i-am-bee/bee-api/issues/134)) ([e997180](https://github.com/i-am-bee/bee-api/commit/e9971803ae1f2e0d11f3a2d4cc0f4c194e83fb5a))
* **readFile:** fix error for read file tool ([#133](https://github.com/i-am-bee/bee-api/issues/133)) ([05a59d9](https://github.com/i-am-bee/bee-api/commit/05a59d98b035bde280c71284b43c005a604d2128))

## [0.0.17](https://github.com/i-am-bee/bee-api/compare/v0.0.16...v0.0.17) (2024-12-13)

### Features

* **deps:** upgrade bee-agent-framework ([#135](https://github.com/i-am-bee/bee-api/issues/135)) ([ede714f](https://github.com/i-am-bee/bee-api/commit/ede714f7557bcb336f36385c74a6580c9e4d68f3))

## [0.0.16](https://github.com/i-am-bee/bee-api/compare/v0.0.15...v0.0.16) (2024-12-12)

### Bug Fixes

* **log:** update logged properties ([#130](https://github.com/i-am-bee/bee-api/issues/130)) ([e6ee1ec](https://github.com/i-am-bee/bee-api/commit/e6ee1ec5ed071b025b604a6410c5865279659f83))

## [0.0.15](https://github.com/i-am-bee/bee-api/compare/v0.0.14...v0.0.15) (2024-12-11)

### Features
Expand Down
180 changes: 180 additions & 0 deletions docs/add-native-tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
## Implement a Custom Native Typescript Tool (all the way to the UI)

In this example we will implement the tool `RiddleTool`, all the way to the [Bee UI](https://github.com/i-am-bee/bee-ui).

### Bee API

### Create a new custom tool

1. Create a new file in *src/runs/execution/tools* directory. In this example we will create *riddle-tool.ts*.
2. Copy and paste the [riddle example base tool](https://github.com/i-am-bee/bee-agent-framework/blob/main/examples/tools/custom/base.ts). In *riddle-tool.ts*.

### Implement the tool in helpers and services

#### Add the tool in *src/runs/execution/tools/helpers.ts* file.

1. Import the tool in the file:

```typescript
import { RiddleTool } from "./riddle-tool.js";
```

2. Append the tool to the array `tools` in the `getTools` function:

```typescript
export async function getTools(run: LoadedRun, context: AgentContext): Promise<FrameworkTool[]> {
const tools: FrameworkTool[] = [];

const vectorStores = getRunVectorStores(run.assistant.$, run.thread.$);
for (const vectorStore of vectorStores) {
vectorStore.lastActiveAt = new Date();
}

// Add the tool
const riddleUsage = run.tools.find(
(tool): tool is SystemUsage =>
tool.type === ToolType.SYSTEM && tool.toolId === SystemTools.RIDDLE
);
if (riddleUsage) {
tools.push(new RiddleTool());
}

...
```
3. Add the tool in `createToolCall` function:
```typescript
...
// Add the tool in the `else if` branch
} else if (tool instanceof RiddleTool) {
return new SystemCall({
toolId: SystemTools.RIDDLE,
input: await tool.parse(input)
});
}
throw new Error(`Unknown tool: ${tool.name}`);
}
```
4. Add the tool in `finalizeToolCall` function:

```typescript
...
// Add the tool in the `switch` statement
case SystemTools.RIDDLE: {
// result can be an instance of arbitrary class
if (!(result instanceof StringToolOutput)) throw new TypeError();
toolCall.output = result.result;
break;
}
}
````

#### Add the tool definition in *src/tools/entities/tool-calls/system-call.entity.ts* file:

```typescript
export enum SystemTools {
WEB_SEARCH = 'web_search',
WIKIPEDIA = 'wikipedia',
WEATHER = 'weather',
ARXIV = 'arxiv',
READ_FILE = 'read_file',
RIDDLE = 'riddle', // Add the tool definition
}
```

#### Set the tool in the handlers in *src/tools/tools.service.ts* file:

1. Import the tool in the file:

```typescript
import { RiddleTool } from '@/runs/execution/tools/riddle-tool.js';
```

2. Instance the tool in the `getSystemTools` function:

```typescript
function getSystemTools() {
...
const systemTools = new Map<string, SystemTool>();
const riddleTool = new RiddleTool(); // Add this line
...
```
3. Set the tool at the end of `getSystemTools` function:

```typescript
...
// add this block of code
systemTools.set('riddle', {
type: ToolType.SYSTEM,
id: 'riddle',
createdAt: new Date('2024-11-22'),
...riddleTool,
inputSchema: riddleTool.inputSchema.bind(riddleTool),
isExternal: false, // true if it accesses public internet
metadata: {
$ui_description_short:
'It generates a random puzzle to test your knowledge.'
},
userDescription:
'It generates a random puzzle to test your knowledge.'
});
return systemTools;
}
```
4. Add the tool in `listTools` function:

```typescript
...
const systemTools: (SystemTool | undefined)[] =
!type || type.includes(ToolType.SYSTEM)
? [
allSystemTools.get(SystemTools.WEB_SEARCH),
allSystemTools.get(SystemTools.WIKIPEDIA),
allSystemTools.get(SystemTools.WEATHER),
allSystemTools.get(SystemTools.ARXIV),
allSystemTools.get('read_file'),
allSystemTools.get(SystemTools.RIDDLE) // add this line
]
: [];
...
```
That's it! You have implemented the tool in the Bee API. :rocket:

### Bee UI

For the tool to be available in the UI, you need to follow these steps:

1. Regenerate types (file *src/app/api/schema.d.ts* should change):

```bash
pnpm schema:generate:api
```

2. Add the tool to `SYSTEM_TOOL_NAME` and `SYSTEM_TOOL_ICONS`in *src/modules/tools/hooks/useToolInfo.tsx* file:

```typescript
const SYSTEM_TOOL_NAME: Record<SystemToolId, string> = {
wikipedia: 'Wikipedia',
web_search: 'WebSearch',
weather: 'OpenMeteo',
arxiv: 'Arxiv',
read_file: 'ReadFile',
riddle: 'Riddle', // Add this line
};
const SYSTEM_TOOL_ICONS: Record<SystemToolId, ComponentType> = {
wikipedia: Wikipedia,
web_search: IbmWatsonDiscovery,
weather: PartlyCloudy,
arxiv: Arxiv,
read_file: DocumentView,
riddle: Code, // Add this line
};
```

That's it! You have implemented the tool in the Bee UI. :rocket:
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bee-api",
"version": "0.0.15",
"version": "0.0.20",
"license": "Apache-2.0",
"author": "IBM Corp.",
"type": "module",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/artifacts/artifacts.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ export async function updateArtifact({
}
artifact.sourceCode = getUpdatedValue(source_code, artifact.sourceCode);
}
if (shared === true) {
if (!artifact.accessToken && shared === true) {
artifact.accessToken = getToken();
} else if (shared === false) {
} else if (artifact.accessToken && shared === false) {
artifact.accessToken = undefined;
}
await ORM.em.flush();
Expand Down
1 change: 1 addition & 0 deletions src/rate-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const redis = createRedisClient({
export const rateLimitPlugin: FastifyPluginAsync = fp.default(async (app) => {
await app.register(rateLimit, {
global: true,
enableDraftSpec: true,
max: (request: FastifyRequest) => {
const authType = determineAuthType(request);
switch (authType.type) {
Expand Down
32 changes: 18 additions & 14 deletions src/runs/execution/tools/read-file-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { Emitter } from 'bee-agent-framework/emitter/emitter';

import { File } from '@/files/entities/file.entity.js';
import { getExtractedText } from '@/files/extraction/helpers';
import { getJobLogger } from '@/logger';

export interface ReadFileToolOptions extends BaseToolOptions {
fileSize: number;
Expand Down Expand Up @@ -65,25 +66,28 @@ export class ReadFileTool extends Tool<StringToolOutput, ReadFileToolOptions> {
if (!file) {
throw new ToolError(`File ${filename} not found.`);
}
let text: string;
try {
const text = await getExtractedText(file, run.signal);
if (text.length > this.options.fileSize) {
throw new ToolError(
`The text is too big (${text.length} characters). Maximum allowed size is ${this.options.fileSize} characters`,
[],
{
isFatal: false,
isRetryable: true
}
);
}
text = await getExtractedText(file, run.signal);
} catch (err) {
getJobLogger('runs').warn({ err }, 'Failed to get extracted text.');

return new StringToolOutput('file content: \n' + text);
} catch {
throw new ToolError('This file is not a text file and can not be read.', [], {
throw new ToolError('Unable to read text from the file.', [], {
isFatal: false,
isRetryable: true
});
}
if (text.length > this.options.fileSize) {
throw new ToolError(
`The text is too big (${text.length} characters). Maximum allowed size is ${this.options.fileSize} characters`,
[],
{
isFatal: false,
isRetryable: true
}
);
}

return new StringToolOutput('file content: \n' + text);
}
}
2 changes: 2 additions & 0 deletions src/runs/runs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ import { ensureRequestContextData } from '@/context.js';
import { getProjectPrincipal } from '@/administration/helpers.js';
import { RUNS_QUOTA_DAILY } from '@/config.js';
import { dayjs, getLatestDailyFixedTime } from '@/utils/datetime.js';
import { updateRateLimitHeadersWithDailyQuota } from '@/utils/rate-limit.js';

export async function assertRunsQuota(newRuns = 1) {
const count = await ORM.em.getRepository(Run).count({
createdBy: getProjectPrincipal(),
createdAt: { $gte: getLatestDailyFixedTime().toDate() }
});
updateRateLimitHeadersWithDailyQuota({ quota: RUNS_QUOTA_DAILY, used: count });
if (count + newRuns > RUNS_QUOTA_DAILY) {
throw new APIError({
message: 'Your daily runs quota has been exceeded',
Expand Down
5 changes: 5 additions & 0 deletions src/streaming/sse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,17 @@
*/

import { FastifyReply } from 'fastify';
import { entries } from 'remeda';

import { Event } from './dtos/event.js';

export const init = (res: FastifyReply) => {
res.hijack();
if (!res.raw.headersSent) {
const headers = res.getHeaders();
entries(headers).forEach(([key, value]) => {
if (value) res.raw.setHeader(key, value);
});
res.raw.setHeader('Content-Type', 'text/event-stream');
res.raw.setHeader('Connection', 'keep-alive');
res.raw.setHeader('Cache-Control', 'no-cache,no-transform');
Expand Down
Loading

0 comments on commit f5ca92d

Please sign in to comment.