Skip to content

Commit 6081ce9

Browse files
committed
Initial commit
1 parent 8f80756 commit 6081ce9

File tree

281 files changed

+179100
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

281 files changed

+179100
-0
lines changed

.github/workflows/docs.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# name: Docs
2+
# on:
3+
# push:
4+
# branches:
5+
# - master
6+
# - main
7+
# permissions:
8+
# contents: write
9+
# jobs:
10+
# deploy:
11+
# runs-on: ubuntu-latest
12+
# steps:
13+
# - uses: actions/checkout@v4
14+
# - name: Configure Git Credentials
15+
# run: |
16+
# git config user.name github-actions[bot]
17+
# git config user.email 41898282+github-actions[bot]@users.noreply.github.com
18+
# - uses: actions/setup-python@v5
19+
# with:
20+
# python-version: 3.x
21+
# - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
22+
# - uses: actions/cache@v4
23+
# with:
24+
# key: mkdocs-material-${{ env.cache_id }}
25+
# path: .cache
26+
# restore-keys: |
27+
# mkdocs-material-
28+
# - run: pip install mkdocs-material
29+
# - run: mkdocs gh-deploy --force

.github/workflows/tests.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
build:
11+
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
python-version: ["3.9", "3.10", "3.11"]
17+
18+
steps:
19+
- uses: actions/checkout@v3
20+
- name: Set up Python ${{ matrix.python-version }}
21+
uses: actions/setup-python@v4
22+
with:
23+
python-version: ${{ matrix.python-version }}
24+
- name: Display Python version
25+
- name: Install Project Manager
26+
run: ./shell/uv.sh
27+
- name: Install Synalinks
28+
run: ./shell/install.sh
29+
- name: Run the tests
30+
run: ./shell/test.sh

README.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# 🧠🔗 Synalinks
2+
3+
## What is Synalinks?
4+
5+
Synalinks is an open-source framework that makes it easy to create, evaluate, train, and deploy industry-standard Language Models (LMs) applications. Synalinks follows the principle of *progressive disclosure of complexity*: meaning that simple workflows should be quick and easy, while arbitrarily advanced ones should be possible via a clear path that builds upon what you've already learned.
6+
7+
Synalinks is a *fork of Keras 3* adapted for neuro-symbolic systems and in-context reinforcement learning, an ensemble of techniques that enhance the LMs predictions and accuracy without changing the weights of the model. We also provide techniques and optimizers to automatically collect data in order to train and fine-tune your own LMs. The goal of Synalinks is to facilitate the rapid setup of simple applications while providing the flexibility for researchers and advanced users to develop sophisticated systems.
8+
9+
## Who is Synalinks for?
10+
11+
Synalinks is designed for a diverse range of users, from professionals and AI researchers to students, independent developers, and hobbyists. It is suitable for anyone who wants to learn about AI by building/composing blocks or build solid foundations for enterprise-grade products. While a background in Machine Learning and Deep Learning can be advantageous — as Synalinks leverages design patterns from Keras, one of the most user-friendly and popular Deep Learning frameworks — it is not a prerequisite. Synalinks is designed to be accessible to anyone with basic programming skills in Python, making it a versatile and inclusive platform for AI development.
12+
13+
## Why use Synalinks?
14+
15+
Developping a successful LM application in a profesional context, beyond stateless chatbots, is difficult and typically include:
16+
17+
- Building optimized prompts with examples/hints at each step
18+
- Pipelines that change over time
19+
- Assessing the performance of your application
20+
- Configuring Language Models
21+
- Building data extraction pipelines
22+
- Documenting your ML workflows
23+
- Using advanced in-context RL techniques and symbolic systems
24+
- Traning, evaluating and versioning the prompts/pipelines
25+
- Deploying REST APIs
26+
27+
Synalinks can help you simplify these tasks by leveraging decade old practices in Deep Learning frameworks. We provide a comprehensive suite of tools and features designed to streamline the development process, making it easier to create, evaluate, train, document and deploy robust neuro-symbolic LMs applications.
28+
29+
### Install
30+
31+
```shell
32+
pip install synalinks
33+
```
34+
35+
## Programming your application: 3 ways
36+
37+
### Using the `Functional` API
38+
39+
You start from `Input`, you chain modules calls to specify the model's forward pass, and finally, you create your model from inputs and outputs:
40+
41+
```python
42+
import synalinks
43+
44+
class Query(synalinks.DataModel):
45+
query: str
46+
47+
class AnswerWithRationale(synalinks.DataModel):
48+
rationale: str
49+
answer: str
50+
51+
language_model = synalinks.LanguageModel(model="ollama/mistral")
52+
53+
x0 = synalinks.Input(data_model=Query)
54+
x1 = synalinks.Generator(
55+
data_model=AnswerWithRationale,
56+
language_model=language_model
57+
)(x0)
58+
59+
program = synalinks.Program(
60+
inputs=x0,
61+
outputs=x1,
62+
name="chain_of_thought",
63+
description="Usefull to answer in a step by step manner.",
64+
)
65+
```
66+
67+
### Subclassing the `Program` class
68+
69+
In that case, you should define your modules in `__init__()` and you should implement the model's forward pass in `call()`.
70+
71+
**Note:** you can optionaly have a `training` argument (boolean), which you can use to specify a different behavior in training and inference.
72+
73+
```python
74+
import synalinks
75+
76+
class Query(synalinks.DataModel):
77+
query: str
78+
79+
class AnswerWithRationale(synalinks.DataModel):
80+
rationale: str
81+
answer: str
82+
83+
class ChainOfThought(synalinks.Program):
84+
"""Usefull to answer in a step by step manner.
85+
86+
The first line of the docstring is provided as description
87+
for the program if not provided in the `super().__init__()`.
88+
In a similar way the name is automatically infered based on
89+
the class name if not provided.
90+
"""
91+
92+
def __init__(self, language_model=None):
93+
super().__init__()
94+
self.answer = synalinks.Generator(
95+
data_model=AnswerWithRationale,
96+
language_model=language_model
97+
)
98+
99+
def call(self, inputs):
100+
x = self.answer(inputs)
101+
return x
102+
103+
program = ChainOfThought(language_model=language_model)
104+
```
105+
106+
### Using the `Sequential` API
107+
108+
In addition, `Sequential` is a special case of program where the program
109+
is purely a stack of single-input, single-output modules.
110+
111+
```python
112+
import synalinks
113+
114+
class Query(synalinks.DataModel):
115+
query: str
116+
117+
class AnswerWithRationale(synalinks.DataModel):
118+
rationale: str
119+
answer: str
120+
121+
language_model = synalinks.LanguageModel(model="ollama/mistral")
122+
123+
program = synalinks.Sequential(
124+
[
125+
synalinks.Input(
126+
data_model=Query,
127+
),
128+
synalinks.Generator(
129+
data_model=AnswerWithRationale,
130+
language_model=language_model,
131+
),
132+
],
133+
name="chain_of_thought",
134+
description="Usefull to answer in a step by step manner.",
135+
)
136+
```
137+
138+
## Getting summary of your program
139+
140+
To print a tabular summary of your program:
141+
142+
```python
143+
program.summary()
144+
```
145+
146+
Or a plot (usefull to document your system):
147+
148+
```python
149+
synalinks.utils.plot_program(
150+
program,
151+
show_trainable=True,
152+
show_schema=True,
153+
)
154+
```
155+
156+
## Running your program
157+
158+
To run your program use the following:
159+
160+
```python
161+
result = program(Query(query="What is the French city of aerospace?"))
162+
163+
print(result)
164+
```
165+
166+
## Training your program
167+
168+
```python
169+
(x_train, y_train), (x_test, y_test) = synalinks.datasets.gsm8k.load_data()
170+
171+
program.compile(
172+
reward=synalinks.rewards.ExactMatch(in_mask=["answer"]),
173+
optimizer=synalinks.optimizers.RandomFewShot()
174+
)
175+
176+
batch_size=32
177+
epochs=2
178+
179+
program.fit(
180+
x_train,
181+
y_train,
182+
batch_size=batch_size,
183+
epochs=epochs,
184+
validation_split=0.15,
185+
)
186+
187+
metrics = program.evaluate(x_test, y_test, verbose=0, return_dict=True)
188+
```
189+
190+
### Learn more
191+
192+
You can learn more on how to create your RAG/Graph-RAG or Agentic applications by reading our [documentation](docs/) or [notebooks](notebooks/). If you have questions about our vision or approach, the [FAQ]() might help you.
193+
194+
### Contributions
195+
196+
Contributions are welcome, either for the implementation of additional modules, metrics, optimizers or for database integrations.
197+
For more information, or help for implementing your ideas (or ones from a paper), please join our discord.
198+
199+
Beware that every additional metric/module/optimizer should be approved by the core team, we want to keep the library minimal and clean as possible to avoid an uncontrolled growth leading to bad software practices like in most current leading LM frameworks.
200+
201+
### Community
202+
203+
[Discord]()
204+
205+
Join our community to learn more about neuro-symbolic systems and the future of AI. We welcome the participation of people from very different backgrounds or education levels.
206+
207+
### Credit
208+
209+
Synalinks would not be possible without the great work of the following open-source projects:
210+
211+
- [Keras](https://keras.io/) for the graph-based computation backbone, API and overall code, design and philosophy
212+
- [DSPy](https://dspy.ai/) for the modules/optimizers inspiration
213+
- [AdalFlow](https://github.com/SylphAI-Inc/AdalFlow) for the the use of jinja2 templates in a self-contained prompt
214+
- [Pydantic](https://docs.pydantic.dev/latest/) for the backend data layer

api_gen.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""Script to generate synalinks public API in `synalinks/api` directory.
2+
3+
Usage:
4+
5+
Run via `./shell/api_gen.sh`.
6+
It generates API and formats user and generated APIs.
7+
"""
8+
9+
import importlib
10+
import os
11+
import shutil
12+
13+
import namex
14+
15+
PACKAGE = "synalinks"
16+
BUILD_DIR_NAME = "tmp_build_dir"
17+
18+
19+
def ignore_files(_, filenames):
20+
return [f for f in filenames if f.endswith("_test.py")]
21+
22+
23+
def copy_source_to_build_directory(root_path):
24+
# Copy sources (`synalinks/` directory and setup files) to build dir
25+
build_dir = os.path.join(root_path, BUILD_DIR_NAME)
26+
if os.path.exists(build_dir):
27+
shutil.rmtree(build_dir)
28+
os.mkdir(build_dir)
29+
shutil.copytree(PACKAGE, os.path.join(build_dir, PACKAGE), ignore=ignore_files)
30+
return build_dir
31+
32+
33+
def export_version_string(api_init_fname):
34+
with open(api_init_fname) as f:
35+
contents = f.read()
36+
with open(api_init_fname, "w") as f:
37+
contents += "from synalinks.src.version import __version__\n"
38+
f.write(contents)
39+
40+
41+
def update_package_init(template_fname, dest_fname, api_module):
42+
with open(template_fname) as template_file:
43+
with open(dest_fname, "w") as dest_file:
44+
for line in template_file:
45+
if "# DO NOT EDIT." in line:
46+
dest_file.write(line)
47+
# Import all public symbols from `api/` and `__version__`.
48+
for symbol in api_module.__dict__.keys():
49+
if symbol.startswith("_") and symbol != "__version__":
50+
continue
51+
dest_file.write(f"from synalinks.api import {symbol}\n")
52+
# Skip the previous autogenerated block.
53+
for line in template_file:
54+
if "# END DO NOT EDIT." in line:
55+
break
56+
dest_file.write(line)
57+
58+
59+
def build():
60+
# Backup the `synalinks/__init__.py` and restore it on error in api gen.
61+
root_path = os.path.dirname(os.path.abspath(__file__))
62+
code_api_dir = os.path.join(root_path, PACKAGE, "api")
63+
code_init_fname = os.path.join(root_path, PACKAGE, "__init__.py")
64+
# Create temp build dir
65+
build_dir = copy_source_to_build_directory(root_path)
66+
build_api_dir = os.path.join(build_dir, PACKAGE, "api")
67+
build_init_fname = os.path.join(build_dir, PACKAGE, "__init__.py")
68+
build_api_init_fname = os.path.join(build_api_dir, "__init__.py")
69+
try:
70+
os.chdir(build_dir)
71+
# Generates `synalinks/api` directory.
72+
if os.path.exists(build_api_dir):
73+
shutil.rmtree(build_api_dir)
74+
if os.path.exists(build_init_fname):
75+
os.remove(build_init_fname)
76+
os.makedirs(build_api_dir)
77+
namex.generate_api_files(
78+
"synalinks", code_directory="src", target_directory="api"
79+
)
80+
# Add __version__ to `api/`.
81+
export_version_string(build_api_init_fname)
82+
# Update toplevel init with all `api/` imports.
83+
api_module = importlib.import_module(f"{BUILD_DIR_NAME}.synalinks.api")
84+
85+
update_package_init(code_init_fname, build_init_fname, api_module)
86+
# Copy back the synalinks/api and synalinks/__init__.py from build directory
87+
if os.path.exists(code_api_dir):
88+
shutil.rmtree(code_api_dir)
89+
shutil.copytree(build_api_dir, code_api_dir)
90+
shutil.copy(build_init_fname, code_init_fname)
91+
finally:
92+
# Clean up: remove the build directory (no longer needed)
93+
shutil.rmtree(build_dir)
94+
95+
96+
if __name__ == "__main__":
97+
build()

0 commit comments

Comments
 (0)