Skip to content

Commit 63f5e70

Browse files
committed
Add OPRO and prepare for KG integration
1 parent 51a6d1e commit 63f5e70

File tree

33 files changed

+943
-82
lines changed

33 files changed

+943
-82
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ async def main():
9191
)
9292

9393
language_model = synalinks.LanguageModel(
94-
model="ollama_chat/deepseek-r1",
94+
model="ollama/mistral",
9595
)
9696

9797
x0 = synalinks.Input(data_model=Query)
@@ -190,7 +190,7 @@ async def main():
190190
return cls(language_model=language_model, **config)
191191

192192
language_model = synalinks.LanguageModel(
193-
model="ollama_chat/deepseek-r1",
193+
model="ollama/mistral",
194194
)
195195

196196
program = ChainOfThought(
@@ -260,7 +260,7 @@ async def main():
260260
)
261261

262262
language_model = synalinks.LanguageModel(
263-
model="ollama_chat/deepseek-r1",
263+
model="ollama/mistral",
264264
)
265265

266266
program = ChainOfThought(
@@ -298,7 +298,7 @@ async def main():
298298
)
299299

300300
language_model = synalinks.LanguageModel(
301-
model="ollama_chat/deepseek-r1",
301+
model="ollama/mistral",
302302
)
303303

304304
program = synalinks.Sequential(

coverage-badge.svg

Lines changed: 1 addition & 1 deletion
Loading
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Your First Programs
2+
3+
The main concept of Synalinks, is that an application (we call it a `Program`)
4+
is a computation graph (a Directed Acyclic Graph to be exact) with JSON data (called `JsonDataModel`) as edges and `Operation`s as nodes.
5+
6+
What set apart Synalinks from other similar frameworks like DSPy or AdalFlow is that we focus on graph-based systems but also that it allow users to declare the computation graph using a Functional API inherited
7+
from [Keras](https://keras.io/).
8+
9+
About modules, similar to layers in deep learning applications, modules are
10+
composable blocks that you can assemble in multiple ways. Providing a modular
11+
and composable architecture to experiment and unlock creativity.
12+
13+
Note that each `Program` is also a `Module`! Allowing you to encapsulate them
14+
as you want.
15+
16+
Many people think that what enabled the Deep Learning revolution was compute
17+
and data, but in reality, frameworks also played a pivotal role as they enabled
18+
researchers and engineers to create complex architectures without having to
19+
re-implement everything from scatch.
20+
21+
```python
22+
import synalinks
23+
import asyncio
24+
# Now we can define the data models that we are going to use in the tutorial.
25+
26+
class Query(synalinks.DataModel):
27+
query: str = synalinks.Field(
28+
description="The user query",
29+
)
30+
31+
class AnswerWithThinking(synalinks.DataModel):
32+
thinking: str = synalinks.Field(
33+
description="Your step by step thinking process",
34+
)
35+
answer: str = synalinks.Field(
36+
description="The correct answer",
37+
)
38+
39+
# And the language model to use
40+
41+
language_model = synalinks.LanguageModel(
42+
model="ollama/mistral",
43+
)
44+
```
45+
46+
## Functional API
47+
48+
You can program your application using 4 different ways, let's start with the
49+
Functional way.
50+
51+
In this case, you start from `Input` and you chain modules calls to specify the
52+
programs's structure, and finally, you create your program from inputs and outputs:
53+
54+
```python
55+
56+
async def main():
57+
58+
x0 = synalinks.Input(data_model=Query)
59+
x1 = await synalinks.Generator(
60+
data_model=AnswerWithThinking,
61+
language_model=language_model,
62+
)(x0)
63+
64+
program = synalinks.Program(
65+
inputs=x0,
66+
outputs=x1,
67+
name="chain_of_thought",
68+
description="Useful to answer in a step by step manner.",
69+
)
70+
71+
if __name__ == "__main__":
72+
asyncio.run(main())
73+
```
74+
75+
## Subclassing the `Program` class
76+
77+
Now let's try to program it using another method, subclassing the `Program`
78+
class. It is the more complicated one, and reserved for skilled developers or contributors.
79+
80+
In that case, you should define your modules in `__init__()` and you should
81+
implement the program's structure in `call()` and the serialization methods (`get_config` and `from_config`).
82+
83+
```python
84+
class ChainOfThought(synalinks.Program):
85+
"""Useful to answer in a step by step manner.
86+
87+
The first line of the docstring is provided as description for the program
88+
if not provided in the `super().__init__()`. In a similar way the name is
89+
automatically infered based on 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=AnswerWithThinking, language_model=language_model
96+
)
97+
98+
async def call(self, inputs, training=False):
99+
x = await self.answer(inputs)
100+
return x
101+
102+
def get_config(self):
103+
config = {
104+
"name": self.name,
105+
"description": self.description,
106+
"trainable": self.trainable,
107+
}
108+
language_model_config = {
109+
"language_model": synalinks.saving.serialize_synalinks_object(
110+
self.language_model
111+
)
112+
}
113+
return {**config, **language_model_config}
114+
115+
@classmethod
116+
def from_config(cls, config):
117+
language_model = synalinks.saving.deserialize_synalinks_object(
118+
config.pop("language_model")
119+
)
120+
return cls(language_model=language_model, **config)
121+
122+
program = ChainOfThought(language_model=language_model)
123+
```
124+
125+
Note that the program isn't actually built, this behavior is intended its
126+
means that it can accept any king of input, making the program truly
127+
generalizable.
128+
129+
## Mixing the subclassing and the `Functional` API
130+
131+
This way of programming is recommended to encapsulate your application while providing an easy to use setup.
132+
It is the recommended way for most users as it avoid making your program/agents from scratch.
133+
In that case, you should implement only the `__init__()` and `build()` methods.
134+
135+
```python
136+
137+
class ChainOfThought(synalinks.Program):
138+
"""Useful to answer in a step by step manner."""
139+
140+
def __init__(
141+
self,
142+
language_model=None,
143+
name=None,
144+
description=None,
145+
trainable=True,
146+
):
147+
super().__init__(
148+
name=name,
149+
description=description,
150+
trainable=trainable,
151+
)
152+
self.language_model = language_model
153+
154+
async def build(self, inputs):
155+
outputs = await synalinks.Generator(
156+
data_model=AnswerWithThinking,
157+
language_model=self.language_model,
158+
)(inputs)
159+
160+
# Create your program using the functional API
161+
super().__init__(
162+
inputs=inputs,
163+
outputs=outputs,
164+
name=self.name,
165+
description=self.description,
166+
trainable=self.trainable,
167+
)
168+
169+
program = ChainOfThought(
170+
language_model=language_model,
171+
)
172+
```
173+
174+
Like when using the subclassing method, the program will be built on the fly when called for the first time.
175+
176+
## Using the `Sequential` API
177+
178+
In addition, `Sequential` is a special case of program where the program
179+
is purely a stack of single-input, single-output modules.
180+
181+
```python
182+
183+
async def main():
184+
185+
program = synalinks.Sequential(
186+
[
187+
synalinks.Input(
188+
data_model=Query,
189+
),
190+
synalinks.Generator(
191+
data_model=AnswerWithThinking,
192+
language_model=language_model,
193+
),
194+
],
195+
name="chain_of_thought",
196+
description="Useful to answer in a step by step manner.",
197+
)
198+
199+
if __name__ == "__main__":
200+
asyncio.run(main())
201+
```
202+
203+
## Running your programs
204+
205+
In order to run your program, you just have to call it with the input data model
206+
as argument.
207+
208+
```python
209+
result = await program(
210+
Query(query="What are the key aspects of human cognition?"),
211+
)
212+
```
213+
214+
## Conclusion
215+
216+
Congratulations! You've successfully explored the fundamental concepts of programming
217+
applications using Synalinks.
218+
219+
Now that we know how to program applications, you can learn how to control
220+
the data flow in the next tutorial.
221+
222+
### Key Takeaways
223+
224+
- **Functional API**: Allows you to chain modules to define the program's structure,
225+
providing a clear and intuitive way to build applications.
226+
- **Subclassing**: Offers flexibility and control by defining modules and implementing
227+
the program's structure from scratch within a class.
228+
- **Mixing the subclassing and the Functional API**: Allows to benefit from the
229+
compositionality of the subclassing while having the ease of use of the functional way of programming.
230+
- **Sequential Programs**: Simplifies the creation of linear workflows, making it easy
231+
to stack single-input, single-output modules.
232+
- **Modularity and Composability**: Enables the reuse of components, fostering
233+
creativity and efficiency in application development.

docs/Code Examples/First Steps.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# First Steps
2+
3+
First, install Synalinks, the easiest way is using pip:
4+
5+
```shell
6+
pip install synalinks
7+
```
8+
9+
Or uv (recommended):
10+
11+
```shell
12+
uv pip install synalinks
13+
```
14+
15+
If you want to install it from source (for contributors), then do:
16+
17+
```shell
18+
git clone https://github.com/SynaLinks/synalinks
19+
cd synalinks
20+
./shell/uv.sh # Install uv
21+
./shell/install.sh # Create the virtual env and install Synalinks
22+
```
23+
24+
After this, open a python file or notebook and check the install:
25+
26+
```python
27+
import synalinks
28+
print(synalinks.__version__)
29+
```
30+
31+
Or just create a new project using the following command:
32+
33+
```shell
34+
uv run synalinks init
35+
```
36+
37+
Synalinks use a global context to ensure that each variable/module
38+
have a unique name. Clear it at the beginning of your scripts to
39+
ensure naming reproductability.
40+
41+
```python
42+
# Clear the global context
43+
synalinks.clear_session()
44+
```
45+
46+
Addtionally, you can install Ollama [here](https://ollama.com/) to run
47+
Language Models (LMs) locally, which is very usefull to development.
48+
49+
## Prompting
50+
51+
You will notice that there is no traditional prompting involved in
52+
Synalinks, everything is described as data models in and out.
53+
However we use a prompt template, that will tell the system how to
54+
construct the prompt automatically.
55+
56+
The prompt template is a jinja2 template that describe how to render
57+
the examples, instructions and how to convert them into chat messages:
58+
59+
::: synalinks.src.modules.core.generator.default_prompt_template
60+
61+
The template use the XML tags `<system>...</system>`, `<user>...</user>` and
62+
`<assistant>...</assistant>` to know how to convert the prompt template
63+
into messages. You can modify the default template used by using the
64+
`prompt_template` argument in Synalinks modules. You can notice also,
65+
that we send the inputs's and output's JSON schema to instruct the LMs
66+
how to answer, you can enable/disable that behavior by using `use_inputs_schema`
67+
and `use_outputs_schema` in Synalinks modules. Synalinks use constrained
68+
structured output ensuring that the LMs answer respect the data models
69+
specification (the JSON schema), and is ready to parse, so in theory
70+
we don't need it, except if you use it to provide additional information
71+
to the LMs. You can find more information in the
72+
[`Generator`](https://synalinks.github.io/synalinks/Synalinks%20API/Modules%20API/Core%20Modules/Generator%20module/) documentation.
73+
74+
## Data Models
75+
76+
To provide additional information to the LMs, you can use the data models
77+
`Field`. You can notice that Synalinks use Pydantic as default data backend.
78+
Allowing Synalinks to be compatible out-of-the-box with constrained structured output, FastAPI of FastMCP.
79+
80+
```python
81+
class AnswerWithThinking(synalinks.DataModel):
82+
thinking: str = synalinks.Field(
83+
description="Your step by step thinking",
84+
)
85+
answer: str = synalinks.Field(
86+
description="The correct answer",
87+
)
88+
```
89+
90+
## Conclusion
91+
92+
Usually that will be enough to instruct the LMs, you don't need to modify
93+
the prompt template. Just by adding additional descriptions to the data
94+
models fields you can instruct your system to behave as you want.
95+
If the system needs general instructions about how to behave, you can
96+
use the `instructions` argument in Synalinks modules that will be formatted as
97+
presented in the prompt template.
98+
99+
### Key Takeaways
100+
101+
- **Ease of Integration**: Synalinks seamlessly integrates with existing
102+
Python projects, making it easy to incorporate advanced language
103+
model capabilities without extensive modifications.
104+
- **Structured Outputs**: By using data models and JSON schemas combined with
105+
*constrained structed output*, Synalinks ensures that the LMs responses are structured and ready for parsing, reducing the need for additional post-processing.
106+
- **Customizable Prompts**: The prompt templates in Synalinks are highly
107+
customizable, allowing you to tailor the instructions provided to
108+
the LMs based on your specific use case.
109+
- **Compatibility**: Synalinks use Pydantic as the default data backend
110+
ensures compatibility with structured output, FastAPI or FastMCP.
111+
"""

docs/Synalinks API/Modules API/Merging Modules/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
- [Concat module](Concat module.md)
44
- [And module](And module.md)
55
- [Or module](Or module.md)
6-
- [Xor module](Merging Modules/Xor module.md)
6+
- [Xor module](Xor module.md)

0 commit comments

Comments
 (0)