Skip to content

Commit

Permalink
Support service type annotation, enhance service registration (#647)
Browse files Browse the repository at this point in the history
* support service type annotation, enhance service registration

* Update migration guide

* Improve change log

* Fix load requirements

* Fix get service with client_id

* Fix get_service
  • Loading branch information
oeway authored Aug 6, 2024
1 parent c58a70b commit 3778201
Show file tree
Hide file tree
Showing 39 changed files with 1,155 additions and 595 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ Hypha is an application framework for large-scale data management and AI model s

Hypha server act as a hub for connecting different components through [hypya-rpc](https://github.com/oeway/hypha-rpc).

## Breaking changes
## Change log

The new version of Hypha (0.20.0+) improves the RPC connection to make it more stable and secure, most importantly it supports automatic reconnection when the connection is lost. This also means breaking changes to the previous version. In the new version you will need a new library called `hypha-rpc` (instead of the hypha submodule in the `imjoy-rpc` module) to connect to the server.
### 0.20.12

- New Feature: In order to support large language models' function calling feature, hypha support built-in type annotation. With `hypha-rpc>=0.20.12`, we also support type annotation for the service functions in JSON Schema format. In Python, you can use `Pydantic` or simple python type hint, or directly write the json schema for Javascript service functions. This allows you to specify the inputs spec for functions.
- Add type support for the `hypha` module. It allows you to register a type in the workspace using `register_service_type`, `get_service_type`, `list_service_types`. When registering a new service, you can specify the type and enable type check for the service. The type check will be performed when calling the service function. The type check is only available in Python.
- Fix reconnecton issue in the client.
- Support case conversion, which allows converting the service functions to snake_case or camelCase in `get_service` (Python) or `getService` (JavaScript).
- **Breaking Changes**: In Python, all the function names uses snake case, and in JavaScript, all the function names uses camel case. For example, you should call `server.getService` instead of `server.get_service` in JavaScript, and `server.get_service` instead of `server.getService` in Python.
- **Breaking Changes**: The new version of Hypha (0.20.0+) improves the RPC connection to make it more stable and secure, most importantly it supports automatic reconnection when the connection is lost. This also means breaking changes to the previous version. In the new version you will need a new library called `hypha-rpc` (instead of the hypha submodule in the `imjoy-rpc` module) to connect to the server.

See https://ha.amun.ai for more detailed usage.
2 changes: 2 additions & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
* [Getting Started](/getting-started)
* [Hypha RPC](/hypha-rpc)
* [Migration Guide](/migration-guide)
* [Service Type Annotation](/service-type-annotation)
* [Serverless Functions](/serverless-functions)
* [Serve ASGI Web Apps](/asgi-apps)
* [Development](/development)
4 changes: 2 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,15 @@ if __name__ == "__main__":
Include the following script in your HTML file to load the `hypha-rpc` client:

```html
<script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.1.10/dist/hypha-rpc-websocket.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.20.12/dist/hypha-rpc-websocket.min.js"></script>
```

Use the following code in JavaScript to connect to the server and access an existing service:

```javascript
async function main(){
const server = await hyphaWebsocketClient.connectToServer({"server_url": "http://localhost:9527"})
const svc = await server.getService("hello-world")
const svc = await server.get_service("hello-world")
const ret = await svc.hello("John")
console.log(ret)
}
Expand Down
2 changes: 1 addition & 1 deletion docs/hypha-quick-tour.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@
}
],
"source": [
"plugins = await api.list_plugins()\n",
"plugins = await api.list_apps()\n",
"plugins"
]
},
Expand Down
191 changes: 191 additions & 0 deletions docs/migration-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Migration Guide

## Migrating from Hypha 0.15.x to 0.20.x

In the new Hypha version, several breaking changes have been introduced to improve the RPC connection, making it more stable and secure. Most importantly, it now supports type annotation for Large Language Models (LLMs), which is ideal for building chatbots, AI agents, and more. Additionally, it features automatic reconnection when the connection is lost, ensuring a more reliable connection. Here is a brief guide to help you migrate your code from the old version to the new version.

### Python

#### 1. Use `hypha-rpc` instead of `imjoy-rpc`

To connect to the server, instead of installing the `imjoy-rpc` module, you will need to install the `hypha-rpc` module. The `hypha-rpc` module is a standalone module that provides the RPC connection to the Hypha server. You can install it using pip:

```bash
# pip install imjoy-rpc # previous install
pip install -U hypha-rpc # new install
```

We also changed our versioning strategy, we use the same version number for the server and client, so it's easier to match the client and server versions. For example, `hypha-rpc` version `0.20.12` is compatible with Hypha server version `0.20.12`.

#### 2. Change the imports to use `hypha-rpc`

Now you need to do the following changes in your code:

```python
# from imjoy_rpc import connect_to_server # previous import
from hypha_rpc import connect_to_server # new import
```

#### 3. Use snake_case for all service function names

Previously, we supported both snake_case and camelCase for the function names in Python, but in the new version, we only support snake_case for all function names.

Here is a suggested list of search and replace operations to update your code:
- `.registerService` -> `.register_service`
- `.unregisterService` -> `.unregister_service`
- `.getService` -> `.get_service`
- `.listServices` -> `.list_services`
- `.registerCodec` -> `.register_codec`

Example:

```python
from hypha_rpc import connect_to_server # new import
async def start_server(server_url):
server = await connect_to_server({"server_url": server_url})
# register a service using server.register_service
# `server.registerService` is not supported anymore
info = await server.register_service({
"name": "Hello World",
"id": "hello-world",
"config": {
"visibility": "public"
},
"hello": hello
})

# get a service using server.get_service
# `server.getService` is not supported anymore
svc = await server.get_service("hello-world")
print(await svc.hello("John"))
```

#### 4. Optionally, annotate functions with JSON schema using Pydantic

To make your functions more compatible with LLMs, you can optionally use Pydantic to annotate them with JSON schema. This helps in creating well-defined interfaces for your services.

We created a tutorial to introduce this new feature: [service type annotation](./service-type-annotation.md).

Here is a quick example using Pydantic:

```python
from pydantic import BaseModel, Field
from hypha_rpc.utils.schema import schema_function

class UserInfo(BaseModel):
name: str = Field(..., description="Name of the user")
email: str = Field(..., description="Email of the user")
age: int = Field(..., description="Age of the user")
address: str = Field(..., description="Address of the user")

@schema_function
def register_user(user_info: UserInfo) -> str:
"""Register a new user."""
return f"User {user_info.name} registered"
```

### JavaScript

#### 1. Use `hypha-rpc` instead of `imjoy-rpc`

To connect to the server, instead of using the `imjoy-rpc` module, you will need to use the `hypha-rpc` module. The `hypha-rpc` module is a standalone module that provides the RPC connection to the Hypha server. You can include it in your HTML using a script tag:

```html
<script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.20.12/dist/hypha-rpc-websocket.min.js"></script>
```

We also changed our versioning strategy, we use the same version number for the server and client, so it's easier to match the client and server versions. For example, `hypha-rpc` version `0.20.12` is compatible with Hypha server version `0.20.12`.

#### 2. Change the connection method and use camelCase for service function names

In JavaScript, the connection method and service function names use camelCase.

Here is a suggested list of search and replace operations to update your code:

- `connect_to_server` -> `connectToServer`
- `.register_service` -> `.registerService`
- `.unregister_service` -> `.unregisterService`
- `.get_service` -> `.getService`
- `.list_services` -> `.listServices`
- `.register_codec` -> `.registerCodec`

Here is an example of how the updated code might look:

```html
<script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.1.10/dist/hypha-rpc-websocket.min.js"></script>
<script>
async function main(){
const server = await hyphaWebsocketClient.connectToServer({"server_url": "https://hypha.aicell.io"});
// register a service using server.registerService
const info = await server.registerService({
name: "Hello World",
id: "hello-world",
config: {
visibility: "public"
},
hello: async (name) => `Hello ${name}`
});
// get a service using server.getService
const svc = await server.getService("hello-world");
const ret = await svc.hello("John");
console.log(ret);
}
main();
</script>
```

#### 3. Optionally, manually annotate functions with JSON schema

To make your functions more compatible with LLMs, you can optionally annotate them with JSON schema. This helps in creating well-defined interfaces for your services.

We created a tutorial to introduce this new feature: [service type annotation](./service-type-annotation.md).

Here is a quick example in JavaScript:

```html
<script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.1.10/dist/hypha-rpc-websocket.min.js"></script>
<script>
async function main(){
const server = await hyphaWebsocketClient.connectToServer({"server_url": "https://hypha.aicell.io"});
function getCurrentWeather(location, unit = "fahrenheit") {
if (location.toLowerCase().includes("tokyo")) {
return JSON.stringify({ location: "Tokyo", temperature: "10", unit: unit });
} else if (location.toLowerCase().includes("san francisco")) {
return JSON.stringify({ location: "San Francisco", temperature: "72", unit: unit });
} else if (location.toLowerCase().includes("paris")) {
return JSON.stringify({ location: "Paris", temperature: "22", unit: unit });
} else {
return JSON.stringify({ location: location, temperature: "unknown" });
}
}
const getCurrentWeatherAnnotated = hyphaWebsocketClient.schemaFunction(getCurrentWeather, {
name: "getCurrentWeather",
description: "Get the current weather in a given location",
parameters: {
type: "object",
properties: {
location: { type: "string", description: "The city and state, e.g. San Francisco, CA" },
unit: { type: "string", enum: ["celsius", "fahrenheit"] }
},
required: ["location"]
}
});
await server.registerService({
name: "Weather Service",
id: "weather-service",
getCurrentWeather: getCurrentWeatherAnnotated
});
const svc = await server.getService("weather-service");
const ret = await svc.getCurrentWeather("Tokyo");
console.log(ret);
}
main();
</script>
```

By following this guide, you should be able to smoothly transition your code from Hypha 0.15.x to 0.20.x and take advantage of the new features, including the support for Large Language Models through type annotations and JSON schema generation.
154 changes: 154 additions & 0 deletions docs/service-type-annotation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@

# Tutorial: Using Typing with Hypha for Supporting Large Language Models

To help you understand the new feature, here is a well-structured tutorial for using typing in your Hypha services.

### Python

**Quick Start:**

Here is a simple function in Python with type annotation:

```python
def get_current_weather(location: str, unit: str = "fahrenheit"):
"""Get the current weather in a given location"""
if "tokyo" in location.lower():
return json.dumps({"location": "Tokyo", "temperature": "10", "unit": unit})
elif "san francisco" in location.lower():
return json.dumps({"location": "San Francisco", "temperature": "72", "unit": unit})
elif "paris" in location.lower()):
return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
else:
return json.dumps({"location": location, "temperature": "unknown"})
}
```

We can use the following JSON schema to describe the function signature:

```json
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["location"],
},
},
}
```

With the above JSON schema, we can generate the function call for the `get_current_weather` function in the LLMs. See [the documentation about function calling from OpenAI for more details](https://platform.openai.com/docs/guides/function-calling).

Manual JSON schema generation can be tedious, so we provide decorators `schema_function` and `schema_method` to automatically generate the JSON schema for the function, from the type hints and Pydantic models.

Here is an example of using the `schema_function` decorator with Pydantic models:

```python
from

pydantic import BaseModel, Field
from hypha_rpc.utils.schema import schema_function

class UserInfo(BaseModel):
"""User information."""
name: str = Field(..., description="Name of the user")
email: str = Field(..., description="Email of the user")
age: int = Field(..., description="Age of the user")
address: str = Field(..., description="Address of the user")

@schema_function
def register_user(user_info: UserInfo) -> str:
"""Register a new user."""
return f"User {user_info.name} registered"
```

Now, let's create a service in a Python client, then connect to it with another Python client and a JavaScript client.

### Step-by-Step Guide

**Python Client: Service Registration**

```python
from pydantic import BaseModel, Field
from hypha_rpc import connect_to_server

async def main():
server = await connect_to_server({"server_url": "https://hypha.aicell.io"})

class UserInfo(BaseModel):
name: str = Field(..., description="Name of the user")
email: str = Field(..., description="Email of the user")
age: int = Field(..., description="Age of the user")
address: str = Field(..., description="Address of the user")

@schema_function
def register_user(user_info: UserInfo) -> str:
return f"User {user_info.name} registered"

await server.register_service({
"name": "User Service",
"id": "user-service",
"description": "Service for registering users",
"register_user": register_user
})

import asyncio
loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_forever()
```

**Python Client: Service Usage**

```python
from hypha_rpc import connect_to_server

async def main():
server = await connect_to_server({"server_url": "https://hypha.aicell.io"})
svc = await server.get_service("user-service")

result = await svc.register_user({
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"address": "1234 Main St"
})
print(result)

import asyncio
loop = asyncio.get_event_loop()
loop.create_task(main())
loop.run_forever()
```

**JavaScript Client: Service Usage**

```html
<script src="https://cdn.jsdelivr.net/npm/hypha-rpc@0.1.10/dist/hypha-rpc-websocket.min.js"></script>
<script>
async function main() {
const server = await hyphaWebsocketClient.connectToServer({"server_url": "https://hypha.aicell.io"});
const svc = await server.getService("user-service");
const result = await svc.register_user({
name: "Alice",
email: "alice@example.com",
age: 30,
address: "1234 Main St"
});
console.log(result);
}
main();
</script>
```

This complete tutorial demonstrates how to use typing with Hypha to support Large Language Models, showing service registration in Python and how to connect and use the service from both Python and JavaScript clients.
Loading

0 comments on commit 3778201

Please sign in to comment.