Skip to content

Commit a3e9b5d

Browse files
committed
Migrate non operation methods of the metadatamodel into the datamodel and update deployment doc
1 parent 82b2f25 commit a3e9b5d

File tree

3 files changed

+72
-129
lines changed

3 files changed

+72
-129
lines changed
Lines changed: 44 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
# Building a REST API
22

3-
The optimal approach to developing Web-Apps or Micro-Services using Synalinks involves building REST APIs and deploying them. You can deploy these APIs locally to test your system or on a cloud provider of your choice to scale to millions of users.
3+
The optimal approach to developing web-apps or micro-services using Synalinks involves building REST APIs and deploying them. You can deploy these APIs locally to test your system or on a cloud provider of your choice to scale to millions of users.
44

55
For this purpose, you will need to use FastAPI, a Python library that makes it easy and straightforward to create REST APIs. If you use the default backend, the DataModel will be compatible with FastAPI as their both use Pydantic.
66

7-
First, let's see how a production-ready project is structured.
7+
In this tutorial we are going to make a backend that run locally to test our system.
88

9-
In this tutorial, we will focus only on the backend of your application.
9+
## Project structure
1010

11-
For the purpose of this tutorial, we will skip authentification. But because is is higly dependent on your business usecase/frontend, we will not handle it here.
11+
Your project structure should look like this:
1212

13-
### Project Structure
14-
15-
```sh
13+
```shell
1614
demo/
17-
1815
├── backend/
1916
│ ├── app/
2017
│ │ ├── checkpoint.program.json
@@ -24,31 +21,26 @@ demo/
2421
├── frontend/
2522
│ └── ... (your frontend code)
2623
├── scripts/
27-
│ ├── export_program.py
2824
│ └── train.py (refer to the code examples to learn how to train programs)
2925
├── docker-compose.yml
3026
├── .env.backend
31-
├── .end.frontend
3227
└── README.md
3328
```
3429

35-
### Setting up your environment variables
36-
37-
```env title=".env.backend"
38-
LLM_PROVIDER=ollama_chat/deepseek-r1
39-
EMBEDDING_PROVIDER=ollama/mxbai-embed-large
30+
## Your `requirements.txt` file
4031

41-
LM_PROVIDER_API_BASE=http://localhost:11434
42-
EMBEDDING_PROVIDER_API_BASE=http://localhost:11434
32+
Import additionally any necessary dependency
4333

44-
MLFLOW_URL=http://localhost:5000
45-
46-
PROD_CORS_ORIGIN=http://localhost:3000
34+
```txt title="requirements.txt"
35+
mlflow
36+
fastapi[standard]
37+
uvicorn
38+
synalinks
4739
```
4840

49-
Feel free to add any API key needed for your LM provider
41+
## Creating your endpoint using FastAPI and SynaLinks
5042

51-
### Creating your endpoint using FastAPI and SynaLinks
43+
Now you can create you endpoint using FastAPI.
5244

5345
```python title="main.py"
5446
import argparse
@@ -59,7 +51,6 @@ import mlflow
5951
import uvicorn
6052
from dotenv import load_dotenv
6153
from fastapi import FastAPI
62-
from fastapi.middleware.cors import CORSMiddleware
6354

6455
import synalinks
6556

@@ -70,28 +61,15 @@ mlflow.litellm.autolog()
7061
mlflow.set_tracking_uri(os.getenv("MLFLOW_URL"))
7162

7263
# Set up logging
64+
logger = logging.getLogger(__name__)
7365
logging.basicConfig(
7466
level=logging.INFO,
7567
format="%(asctime)s - %(levelname)s - %(message)s",
7668
)
7769
# Set up FastAPI
7870
app = FastAPI()
7971

80-
# Set up CORS ORIGIN to avoid connexion problems with the frontend
81-
origins = [
82-
"http://localhost",
83-
os.getenv("PROD_CORS_ORIGIN"),
84-
]
85-
86-
app.add_middleware(
87-
CORSMiddleware,
88-
allow_origins=origins,
89-
allow_credentials=True,
90-
allow_methods=["*"],
91-
allow_headers=["*"],
92-
)
93-
94-
# The dictionary mapping the name of your custom modules to the class
72+
# The dictionary mapping the name of your custom modules to their class
9573
custom_modules = {}
9674

9775
# Load your program
@@ -102,72 +80,33 @@ program = synalinks.Program.load(
10280

10381
@app.post("/v1/chat_completion")
10482
async def chat_completion(messages: synalinks.ChatMessages):
105-
result = await program(messages)
106-
return result.json()
83+
logger.info(messages.pretty_json())
84+
try:
85+
result = await program(messages)
86+
if result:
87+
logger.info(result.pretty_json())
88+
return result.json()
89+
else:
90+
return None
91+
except Exception as e:
92+
logger.error(f"Error occured: {str(e)}")
93+
return None
10794

10895

10996
if __name__ == "__main__":
11097
parser = argparse.ArgumentParser()
11198
parser.add_argument("--host", type=str, default="127.0.0.1")
112-
parser.add_argument("--port", type=int, default=80)
99+
parser.add_argument("--port", type=int, default=8000)
113100
args = parser.parse_args()
114101
uvicorn.run(app, host=args.host, port=args.port)
115102
```
116103

117-
### Checkpoint migration
118-
119-
For obvious reasons, you will need to have a separate logic to train your application. This script will specify the program architecture, training and evaluation procedure and will end up saving your program into a serializable JSON format.
120-
121-
Please refer to the code examples and [Training API](https://synalinks.github.io/synalinks/Synalinks%20API/Programs%20API/Program%20training%20API/) to learn how to train your programs.
122-
123-
To ease the migration, we'll also make a small script that export the trained program into our backend folder.
124-
125-
```python title="export_program.py"
126-
import argparse
127-
import os
128-
import shutil
129-
130-
131-
def main():
132-
parser = argparse.ArgumentParser(
133-
description="Copy a serialized program to a specified directory."
134-
)
135-
parser.add_argument(
136-
"filepath",
137-
type=str,
138-
help="Path to the file to be copied.",
139-
)
140-
parser.add_argument(
141-
"output_dir",
142-
type=str,
143-
help="Path to the output directory.",
144-
)
145-
args = parser.parse_args()
146-
147-
if not args.filepath.endswith(".json"):
148-
raise ValueError("The filepath must ends with `.json`")
149-
150-
if not os.path.exists(args.output_dir):
151-
print(f"[*] Output directory does not exist. Creating: '{args.output_dir}'")
152-
os.makedirs(args.output_dir)
153-
154-
filename = os.path.basename(args.filepath)
155-
destination = os.path.join(args.output_dir, filename)
156-
print(f"[*] Copying file from '{args.filepath}' to '{destination}'...")
157-
shutil.copy2(args.filepath, destination)
158-
print(f"[*] Program exported to '{destination}'")
159-
160-
161-
if __name__ == "__main__":
162-
main()
163-
```
164-
165-
### Creating the backend's Dockerfile
104+
## Creating the Dockerfile
166105

167-
According to [FastAPI documentation](https://fastapi.tiangolo.com/deployment/docker/#dockerfile) here is the right Dockerfile to use for your backend.
106+
Here is the dockerfile to use according to FastAPI documentation.
168107

169-
```Dockerfile
170-
FROM python:3.9
108+
```Dockerfile title="Dockerfile"
109+
FROM python:3.13
171110

172111
WORKDIR /code
173112

@@ -177,13 +116,14 @@ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
177116

178117
COPY ./app /code/app
179118

180-
CMD ["fastapi", "run", "app/main.py", "--port", "80"]
119+
CMD ["fastapi", "run", "app/main.py", "--port", "8000"]
181120
```
182121

183-
### Creating your docker-compose.yml file
122+
## The docker compose file
184123

185-
```yaml
186-
version: '3'
124+
And finally your docker compose file.
125+
126+
```yml title="docker-compose.yml"
187127
services:
188128
mlflow:
189129
image: ghcr.io/mlflow/mlflow:latest
@@ -194,20 +134,20 @@ services:
194134
context: ./backend
195135
dockerfile: Dockerfile
196136
ports:
197-
- "80:80"
137+
- "8000:8000"
198138
env_file:
199139
- .env.backend
200-
volumes:
201-
- ./data:/code/data
140+
depends_on:
141+
- mlflow
202142
```
203143
204-
### Launching your application
144+
## Launching your backend
145+
146+
Launch your backend using `docker compose`
205147

206148
```shell
207149
cd demo
208150
docker compose up
209151
```
210152

211-
### Testing your application backend
212-
213-
Open you browser to `http://127.0.0.1/docs` and test your API
153+
Open you browser to `http://0.0.0.0:8000/docs` and test your API with the FastAPI UI

synalinks/src/backend/pydantic/core.py

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,6 @@ class MetaDataModel(type(pydantic.BaseModel)):
2424
Allowing to use Synalinks Python operators with `DataModel` types.
2525
"""
2626

27-
def schema(cls):
28-
"""Gets the JSON schema of the data model.
29-
30-
Returns:
31-
(dict): The JSON schema.
32-
"""
33-
return cls.model_json_schema()
34-
35-
def pretty_schema(cls):
36-
"""Get a pretty version of the JSON schema for display.
37-
38-
Returns:
39-
(str): The indented JSON schema.
40-
"""
41-
return json.dumps(cls.schema(), indent=2)
42-
43-
def to_symbolic_data_model(cls):
44-
"""Converts the data model to a symbolic data model.
45-
46-
Returns:
47-
(SymbolicDataModel): The symbolic data model.
48-
"""
49-
return SymbolicDataModel(schema=cls.schema())
50-
5127
def __add__(cls, other):
5228
"""Concatenates this data model with another.
5329
@@ -194,6 +170,33 @@ class AnswerWithReflection(synalinks.DataModel):
194170
"""
195171

196172
model_config: ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="forbid")
173+
174+
@classmethod
175+
def schema(cls):
176+
"""Gets the JSON schema of the data model.
177+
178+
Returns:
179+
(dict): The JSON schema.
180+
"""
181+
return cls.model_json_schema()
182+
183+
@classmethod
184+
def pretty_schema(cls):
185+
"""Get a pretty version of the JSON schema for display.
186+
187+
Returns:
188+
(str): The indented JSON schema.
189+
"""
190+
return json.dumps(cls.schema(), indent=2)
191+
192+
@classmethod
193+
def to_symbolic_data_model(cls):
194+
"""Converts the data model to a symbolic data model.
195+
196+
Returns:
197+
(SymbolicDataModel): The symbolic data model.
198+
"""
199+
return SymbolicDataModel(schema=cls.schema())
197200

198201
def json(self):
199202
"""Alias for the JSON value of the data model.

synalinks/src/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from synalinks.src.api_export import synalinks_export
44

55
# Unique source of truth for the version number.
6-
__version__ = "0.1.3007"
6+
__version__ = "0.1.3008"
77

88

99
@synalinks_export("synalinks.version")

0 commit comments

Comments
 (0)