Skip to content

Commit ff66005

Browse files
authored
Implement API endpoints for "OpenAI Assistants" client (#59)
* Update docs * Add API endpoint for getting the OpenAI Functions specification * Add "Resolve action from specs" API endpoint * Add "Assistants API" docs and fix typos * Update docs
1 parent b18fc1d commit ff66005

File tree

14 files changed

+259
-15
lines changed

14 files changed

+259
-15
lines changed

apps/docs/docs/clients/native/api.mdx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ Use the Connery API to create your own client based on the Connery plugin infras
66

77
Connery Runner exposes a dynamic OpenAPI schema that describes all the actions available on your runner.
88

9-
To get the OpenAPI schema, send a request to your runner's `GET /v1/actions/openapi` endpoint.
9+
To get the OpenAPI schema, send a request to your runner's `GET /v1/actions/specs/openapi` endpoint.
1010
The endpoint is private and requires an API Key for safety reasons.
1111

1212
Use the following cURL to get the OpenAPI schema:
1313

1414
```
15-
curl -L '<PUBLIC_RUNNER_URL>/v1/actions/openapi' \
15+
curl -L '<PUBLIC_RUNNER_URL>/v1/actions/specs/openapi' \
1616
-H 'x-api-key: <CONNERY_RUNNER_API_KEY>'
1717
```
1818

apps/docs/docs/clients/native/index.mdx

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ Application Clients are applications in various platforms that allow running act
1616

1717
- [Slack App](/docs/clients/native/slack)
1818
- [Make App](/docs/clients/native/make)
19-
- [OpenAI GPTs](/docs/clients/native/openai)
19+
- [OpenAI GPTs](/docs/clients/native/openai/gpt)
20+
- [OpenAI Assistants API](/docs/clients/native/openai/assistant)
2021

2122
## API Clients
2223

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Assistants API
2+
3+
Use Connery actions in Function Calling tool of OpenAI Assistants API.
4+
5+
## Information
6+
7+
The Assistants API allows you to build AI assistants within your own applications.
8+
An Assistant has instructions and can leverage models, tools, and knowledge to respond to user queries.
9+
The Assistants API currently supports three types of tools: Code Interpreter, Retrieval, and _Function Calling_.
10+
11+
Function calling allows you to describe functions to the Assistants and have it intelligently return the functions that need to be called along with their arguments.
12+
The Assistants API will pause execution during a Run when it invokes functions, and you can supply the results of the function call back to continue the Run execution.
13+
14+
Connery actions can easily be used in the Function Calling tool of OpenAI Assistants API.
15+
See below for instructions on configuring and using Connery actions in the Assistant.
16+
17+
:::note Helpful resources
18+
19+
- [OpenAI Assistants API](https://platform.openai.com/docs/assistants/overview/agents)
20+
- [Funtion Calling](https://platform.openai.com/docs/assistants/tools/function-calling)
21+
22+
:::
23+
24+
## Configuration
25+
26+
### Prerequisites
27+
28+
If you are new to Connery, we recommend you start with the [Quickstart](/docs/platform/quick-start/) guide.
29+
There, you will find all the information required to set up Connery and start using it.
30+
31+
### Configure functions in the Assistant
32+
33+
1. **Get OpenAI Functions specification for Connery actions installed on the runner.**
34+
35+
```
36+
curl -L '<PUBLIC_RUNNER_URL>/v1/actions/specs/openai-functions' \
37+
-H 'x-api-key: <CONNERY_RUNNER_API_KEY>'
38+
```
39+
40+
The `<PUBLIC_RUNNER_URL>` and `<CONNERY_RUNNER_API_KEY>` you should get during the runner setup in the [Quickstart](/docs/platform/quick-start/) guide.
41+
42+
In response, you will get an OpenAI Function specification for every action installed on your Connery runner.
43+
44+
2. **Use the specification to add functions to your assistant.**
45+
46+
You can do it using Assistant API.
47+
See the [Defining functions](https://platform.openai.com/docs/assistants/tools/defining-functions) section of the Assistants AI documentation for more information.
48+
49+
Also, you can do it on the [Assistants](https://platform.openai.com/assistants) or [Playground](https://platform.openai.com/playground) pages on the OpenAI developer platform.
50+
51+
<p align="center">
52+
<img
53+
src="/img/openai/[email protected]"
54+
alt="Functions configuration in Assistant"
55+
/>
56+
</p>
57+
58+
:::info
59+
60+
The UI does not allow adding an array of functions at once. So you need to add them one by one.
61+
62+
:::
63+
64+
Once you add the functions, you can use them as any other functions in the Function Calling tool of the Assistants API.
65+
66+
## Usage
67+
68+
1. **Get functions from the model that have to be called.**
69+
70+
When you initiate a Run with a user Message that triggers the function, the model can provide multiple functions.
71+
See the [Reading the functions called by the Assistant](https://platform.openai.com/docs/assistants/tools/reading-the-functions-called-by-the-assistant) section of the Assistants AI documentation for more information.
72+
73+
2. **Resolve the function name to the Connery action.**
74+
75+
```
76+
curl -L '<PUBLIC_RUNNER_URL>/v1/actions/specs/resolve-action-from-specs?specActionKey=<FUNCTION_NAME>' \
77+
-H 'x-api-key: <CONNERY_RUNNER_API_KEY>'
78+
```
79+
80+
Replace `<FUNCTION_NAME>` with the function name you got from the model.
81+
82+
In the response you will get the Plugin ID and Action ID which you can then use to run the action in the runner.
83+
84+
:::info
85+
86+
Because of the limitation of the OpenAI Function schema, we can not put the Plugin ID and Action ID in the function name.
87+
So, we use a short hashed action key in the OpenIA Function schema.
88+
Then we resolve it to the Plugin ID and Action ID required to run the action on the runner.
89+
90+
This is a temporary solution which will be adjusted in the future.
91+
92+
:::
93+
94+
3. **Run the action on the runner and get the result.**
95+
96+
Use the following API endpoint to run the action:
97+
98+
```
99+
curl -L '<PUBLIC_RUNNER_URL>/v1/plugins/<PLUGIN_ID>/actions/<ACTION_ID>/run' \
100+
-H 'Content-Type: application/json' \
101+
-H 'x-api-key: <CONNERY_RUNNER_API_KEY>' \
102+
-d '<ARGUMENTS>'
103+
```
104+
105+
Replace `<PLUGIN_ID>` and `<ACTION_ID>` with the values you got from the previous step.
106+
107+
Replace `<ARGUMENTS>` with the arguments you got from the model.
108+
It should be a flat JSON object like this:
109+
110+
```
111+
{
112+
"argument1": "value1",
113+
"argument2": "value2"
114+
}
115+
```
116+
117+
After the action is executed, you will get the result in the response.
118+
119+
4. **Send the result back to the model.**
120+
121+
See the [Submitting functions outputs](https://platform.openai.com/docs/assistants/tools/submitting-functions-outputs) section of the Assistants AI documentation for more information.
122+
123+
:::tip One action - multiple opportunities
124+
125+
Once the action is configured on Connery Runner, you can use it not only in the Assistants API but also on many other platforms.
126+
Check the list of [Clients](/docs/clients/native/) to see what platforms are supported.
127+
128+
:::

apps/docs/docs/clients/native/openai.mdx renamed to apps/docs/docs/clients/native/openai/gpt.mdx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# OpenAI GPTs
1+
# GPTs
22

33
Use Connery actions in your OpenAI GPTs to make them more powerful.
44

@@ -68,13 +68,13 @@ Once you have a Connery runner up and running and a GPT created, you can add Con
6868

6969
1. **Get OpenAPI schema** with all the actions installed on your runner
7070

71-
To get the OpenAPI schema, you must send a request to your runner's `GET /v1/actions/openapi` endpoint.
71+
To get the OpenAPI schema, you must send a request to your runner's `GET /v1/actions/specs/openapi` endpoint.
7272
The endpoint is private and requires an API Key for safety reasons.
7373

7474
Use the following cURL to get the OpenAPI schema:
7575

7676
```
77-
curl -L '<PUBLIC_RUNNER_URL>/v1/actions/openapi' \
77+
curl -L '<PUBLIC_RUNNER_URL>/v1/actions/specs/openapi' \
7878
-H 'x-api-key: <CONNERY_RUNNER_API_KEY>'
7979
```
8080

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import DocCardList from "@theme/DocCardList";
2+
3+
# OpenAI
4+
5+
<DocCardList />

apps/docs/docs/platform/use-cases/index.mdx

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ We plan to add more clients for other AI Agent and Chatbot platforms.
3232

3333
:::note Related Clients
3434

35-
- [OpenAI GPTs](/docs/clients/native/openai)
35+
- [OpenAI GPTs](/docs/clients/native/openai/gpt)
36+
- [OpenAI Assistants API](/docs/clients/native/openai/assistant)
3637
- [LangChain Tool](/docs/clients/native/langchain)
3738

3839
:::

apps/docs/docs/platform/use-cases/send-email-from-a-custom-openai-gpt-using-connery-actions.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ significantly boosting productivity and the overall utility of the AI tool.
3030
## Related links to set up a similar solution
3131

3232
- Plugin, its source code, and documentation for this use case: [connery-io/gmail](https://github.com/connery-io/gmail)
33-
- Client and its documentation for this use case: [OpenAI GPTs](/docs/clients/native/openai)
33+
- Client and its documentation for this use case: [OpenAI GPTs](/docs/clients/native/openai/gpt)
3434
- How to set up Connery: [Quickstart](/docs/platform/quick-start)

apps/docs/sidebars.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,22 @@ const sidebars = {
6969
collapsible: false,
7070
collapsed: false,
7171
items: [
72+
{
73+
type: "category",
74+
label: "OpenAI",
75+
collapsible: false,
76+
collapsed: false,
77+
items: [
78+
'clients/native/openai/gpt',
79+
'clients/native/openai/assistant',
80+
],
81+
link: {
82+
type: 'doc',
83+
id: 'clients/native/openai/index',
84+
},
85+
},
7286
'clients/native/slack',
7387
'clients/native/make',
74-
'clients/native/openai',
7588
'clients/native/api',
7689
'clients/native/cli',
7790
'clients/native/langchain',
57.2 KB
Loading

apps/runner/src/clients-api/actions.controller.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { Body, Controller, Get, Inject, Post } from '@nestjs/common';
1+
import { Body, Controller, Get, HttpException, HttpStatus, Inject, Post, Query } from '@nestjs/common';
22
import { ILlm } from ':src/shared/llm/llm.interface';
33
import { ActionIdentifiedOutput, ActionNotIdentifiedOutput } from ':src/shared/llm/types';
44
import { ObjectResponse } from ':src/shared/types';
55
import { OpenApiForActions } from ':src/shared/openapi-for-actions';
6+
import { OpenAiFunctionsForActions } from ':src/shared/openai-functions-for-actions';
7+
import { IPluginCache } from ':src/shared/plugin-cache/plugin-cache.interface';
68

79
type IdentifyActionBody = {
810
prompt: string;
@@ -13,6 +15,8 @@ export class ActionsController {
1315
constructor(
1416
@Inject(ILlm) private llm: ILlm,
1517
@Inject(OpenApiForActions) private openApiForActions: OpenApiForActions,
18+
@Inject(OpenAiFunctionsForActions) private openAiFunctionsForActions: OpenAiFunctionsForActions,
19+
@Inject(IPluginCache) private pluginCache: IPluginCache,
1620
) {}
1721

1822
// This endpoint is deprecated and will be removed in the future
@@ -31,11 +35,41 @@ export class ActionsController {
3135
return this.identifyAction(body);
3236
}
3337

34-
@Get('/v1/actions/openapi')
38+
@Get('/v1/actions/specs/openapi')
3539
async getActionsOpenApi(): Promise<any> {
3640
return this.openApiForActions.getOpenApiSchema();
3741
}
3842

43+
@Get('/v1/actions/specs/openai-functions')
44+
async getOpenAiFunctionsSchemaForActions(): Promise<any> {
45+
return this.openAiFunctionsForActions.getOpenAiFunctionsSchema();
46+
}
47+
48+
// NOTE: This is a temporary endpoint.
49+
// This endpoint resolves specActionKey (short hashed action key) to pluginKey and actionKey,
50+
// so that the client can use the Connery API usign the pluginKey and actionKey.
51+
// This is done becasue the OpenAI and OpenAPI specs have limitations for the action keys,
52+
// so we can not store the full action key (pluginKey/actionKey) in the specs.
53+
@Get('/v1/actions/specs/resolve-action-from-specs')
54+
async resolveActionFromSpecs(
55+
@Query('specActionKey') specActionKey: string,
56+
): Promise<{ pluginKey: string; actionKey: string }> {
57+
if (!specActionKey) {
58+
throw new HttpException(`The query param 'specActionKey' is required.`, HttpStatus.BAD_REQUEST);
59+
}
60+
61+
// At the moment we still use the actionKey as the specActionKey.
62+
// But this should be changed in the future as it's not scalable solution.
63+
// Expecially when we have the same actionKey in multiple plugins.
64+
// TODO: Change the specActionKey to be a short hashed action key.
65+
const action = await this.pluginCache.getAction(specActionKey);
66+
67+
return {
68+
pluginKey: action.plugin.key,
69+
actionKey: action.definition.key,
70+
};
71+
}
72+
3973
private async identifyAction(
4074
body: IdentifyActionBody,
4175
): Promise<ObjectResponse<ActionIdentifiedOutput | ActionNotIdentifiedOutput>> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Inject, Injectable } from '@nestjs/common';
2+
import { IPluginCache } from './plugin-cache/plugin-cache.interface';
3+
import { OpenAiFunctionSchema } from './types';
4+
5+
@Injectable()
6+
export class OpenAiFunctionsForActions {
7+
constructor(@Inject(IPluginCache) private pluginCache: IPluginCache) {}
8+
9+
async getOpenAiFunctionsSchema(): Promise<OpenAiFunctionSchema[]> {
10+
const actions = await this.pluginCache.getActions();
11+
12+
const openAiFunctions: OpenAiFunctionSchema[] = [];
13+
14+
for (const action of actions) {
15+
const openAiFunction: OpenAiFunctionSchema = {
16+
name: action.definition.key,
17+
description: action.definition.description || '',
18+
parameters: {
19+
type: 'object',
20+
properties: {},
21+
required: [],
22+
},
23+
};
24+
25+
for (const inputParameter of action.definition.inputParameters) {
26+
openAiFunction.parameters.properties[inputParameter.key] = {
27+
type: 'string',
28+
description: inputParameter.description || '',
29+
};
30+
31+
if (inputParameter.validation?.required) {
32+
openAiFunction.parameters.required.push(inputParameter.key);
33+
}
34+
}
35+
36+
openAiFunctions.push(openAiFunction);
37+
}
38+
39+
return openAiFunctions;
40+
}
41+
}

apps/runner/src/shared/plugin-cache/memory-cache.service.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { rmSync } from 'fs';
22
import { PluginDownloader } from './plugin-downloader';
3-
import { Inject, Injectable } from '@nestjs/common';
3+
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
44
import { find, filter } from 'lodash';
55
import { IPluginCache } from './plugin-cache.interface';
66
import { ActionRuntime, PluginRuntime } from 'lib';
@@ -33,7 +33,7 @@ export class MemoryCacheService implements IPluginCache {
3333
const plugin = find(this._plugins, (action: PluginRuntime) => action.key === pluginKey);
3434

3535
if (!plugin) {
36-
throw new Error(`The plugin '${pluginKey}' is not found on the runner.`);
36+
throw new HttpException(`The plugin '${pluginKey}' is not found on the runner.`, HttpStatus.NOT_FOUND);
3737
}
3838

3939
return plugin;
@@ -62,7 +62,7 @@ export class MemoryCacheService implements IPluginCache {
6262
) as ActionRuntime[];
6363

6464
if (actions.length === 0) {
65-
throw new Error(`The action '${actionKey}' is not found on the runner.`);
65+
throw new HttpException(`The action '${actionKey}' is not found on the runner.`, HttpStatus.NOT_FOUND);
6666
} else if (actions.length > 1) {
6767
// TODO: handle this case properly
6868
throw new Error(

apps/runner/src/shared/shared.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IConfig } from './config/config.interface';
99
import { IPluginCache } from './plugin-cache/plugin-cache.interface';
1010
import { ILlm } from './llm/llm.interface';
1111
import { OpenApiForActions } from './openapi-for-actions';
12+
import { OpenAiFunctionsForActions } from './openai-functions-for-actions';
1213

1314
@Module({
1415
imports: [ConfigModule],
@@ -30,7 +31,8 @@ import { OpenApiForActions } from './openapi-for-actions';
3031
useClass: OpenAiService,
3132
},
3233
OpenApiForActions,
34+
OpenAiFunctionsForActions,
3335
],
34-
exports: [IConfig, IPluginCache, ILlm, OpenApiForActions],
36+
exports: [IConfig, IPluginCache, ILlm, OpenApiForActions, OpenAiFunctionsForActions],
3537
})
3638
export class SharedModule {}

apps/runner/src/shared/types.ts

+19
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,22 @@ export type ErrorResponse = {
1818
message: string;
1919
};
2020
};
21+
22+
//
23+
// Other types
24+
//
25+
26+
export type OpenAiFunctionSchema = {
27+
name: string;
28+
description: string;
29+
parameters: {
30+
type: 'object';
31+
properties: {
32+
[key: string]: {
33+
type: 'string';
34+
description: string;
35+
};
36+
};
37+
required: string[];
38+
};
39+
};

0 commit comments

Comments
 (0)