File size: 9,102 Bytes
fe643f6 fa1e750 fe643f6 fa1e750 fe643f6 fa1e750 fe643f6 fa1e750 fe643f6 45d9b08 fe643f6 45d9b08 fe643f6 feb47ff fe643f6 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
import marimo
__generated_with = "0.11.9"
app = marimo.App()
@app.cell(hide_code=True)
def _():
import marimo as mo
import synalinks
synalinks.backend.clear_session()
return mo, synalinks
@app.cell(hide_code=True)
def _(mo):
mo.md(
r"""
# Your first programs
The main concept of Synalinks, is that an application (we call it a `Program`)
is a computation graph with JSON data (called `JsonDataModel`) as edges and
`Operation`s as nodes. 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
from [Keras](https://keras.io/).
About modules, similar to layers in deep learning applications, modules are
composable blocks that you can assemble in multiple ways. Providing a modular
and composable architecture to experiment and unlock creativity.
Note that each `Program` is also a `Module`! Allowing you to encapsulate them
as you want.
Many people think that what enabled the Deep Learning revolution was compute
and data, but in reality, frameworks also played a pivotal role as they enabled
researchers and engineers to create complex architectures without having to
re-implement everything from scatch.
"""
)
return
@app.cell
def _(synalinks):
# Now we can define the data models that we are going to use in the notebook.
# Note that Synalinks use Pydantic as default data backend, which is compatible with FastAPI and structured output.
class Query(synalinks.DataModel):
query: str = synalinks.Field(
description="The user query",
)
class AnswerWithThinking(synalinks.DataModel):
thinking: str = synalinks.Field(
description="Your step by step thinking process",
)
answer: str = synalinks.Field(
description="The correct answer",
)
return AnswerWithThinking, Query
@app.cell(hide_code=True)
def _(mo):
mo.md(
r"""
## Functional API
You can program your application using 3 different ways, let's start with the
Functional way.
In this case, you start from `Input` and you chain modules calls to specify the
programs's structure, and finally, you create your program from inputs and outputs:
"""
)
return
@app.cell
async def _(AnswerWithThinking, Query, synalinks):
language_model = synalinks.LanguageModel(
model="openai/gpt-4o-mini",
)
_x0 = synalinks.Input(data_model=Query)
_x1 = await synalinks.Generator(
data_model=AnswerWithThinking,
language_model=language_model,
)(_x0)
program = synalinks.Program(
inputs=_x0,
outputs=_x1,
name="chain_of_thought",
description="Useful to answer in a step by step manner.",
)
return language_model, program
@app.cell
def _(program):
# You can print a summary of your program in a table format
# which is really useful to have a quick overview of your application
program.summary()
return
@app.cell
def _(mo, program, synalinks):
# Or plot your program in a graph format
synalinks.utils.plot_program(
program,
show_module_names=True,
show_trainable=True,
show_schemas=True,
)
return
@app.cell(hide_code=True)
def _(mo):
mo.md(
r"""
## Subclassing the `Program` class
Now let's try to program it using another method, subclassing the `Program`
class.
In that case, you should define your modules in `__init__()` and you should
implement the program's structure in `call()`.
"""
)
return
@app.cell
def _(AnswerWithThinking, language_model, synalinks):
class ChainOfThought(synalinks.Program):
"""Useful to answer in a step by step manner.
The first line of the docstring is provided as description for the program
if not provided in the `super().__init__()`. In a similar way the name is
automatically infered based on the class name if not provided.
"""
def __init__(self, language_model=None):
super().__init__()
self.answer = synalinks.Generator(
data_model=AnswerWithThinking, language_model=language_model
)
async def call(self, inputs, training=False):
x = await self.answer(inputs)
return x
def get_config(self):
config = {
"name": self.name,
"description": self.description,
"trainable": self.trainable,
}
language_model_config = {
"language_model": synalinks.saving.serialize_synalinks_object(
self.language_model
)
}
return {**config, **language_model_config}
@classmethod
def from_config(cls, config):
language_model = synalinks.saving.deserialize_synalinks_object(
config.pop("language_model")
)
return cls(language_model=language_model, **config)
program_1 = ChainOfThought(language_model=language_model)
return ChainOfThought, program_1
@app.cell
def _(program_1):
program_1.summary()
return
@app.cell(hide_code=True)
def _(mo):
mo.md(
r"""
Note that the program isn't actually built, this behavior is intended its
means that it can accept any king of input, making the program truly
generalizable. Now we can explore the last way of programming as well as
illustrate one of the key feature of Synalinks, composability.
## Using `Sequential` program
In addition to the other ways of programming, `Sequential` is a special
case of programs where the program is purely a stack of single-input,
single-output modules.
In this example, we are going to re-use the `ChainOfThought` program that
we defined previously, illustrating the modularity of the framework.
"""
)
return
@app.cell
def _(ChainOfThought, Query, language_model, synalinks):
program_2 = synalinks.Sequential(
[
synalinks.Input(data_model=Query),
ChainOfThought(language_model=language_model),
],
name="chain_of_thought",
description="Useful to answer in a step by step manner.",
)
program_2.summary()
return (program_2,)
@app.cell(hide_code=True)
def _(mo):
mo.md(
r"""
## Running your programs
In order to run your program, you just have to call it with the input data model
as argument.
"""
)
return
@app.cell(hide_code=True)
def _(mo):
openai_api_key = mo.ui.text_area(placeholder="Your OpenAI API key...").form()
openai_api_key
return
@app.cell(hide_code=True)
def _(mo, openai_api_key, litellm):
import litellm
mo.stop(not openai_api_key.value)
litellm.openai_key = openai_api_key.value
return
@app.cell(hide_code=True)
def _(mo):
run_button = mo.ui.run_button(label="Run program")
run_button.center()
return run_button
@app.cell
async def _(Query, program_2):
mo.stop(not openai_api_key.value, mo.md("Provide your OpenAI API key"))
mo.stop(not run_button.value, mo.md("Click on the run button above"))
result = await program_2(
Query(query="What are the key aspects of human cognition?"),
)
print(result.prettify_json())
return (result,)
@app.cell(hide_code=True)
def _(mo):
mo.md(
r"""
## Conclusion
Congratulations! You've successfully explored the fundamental concepts of programming
applications using Synalinks. By understanding and implementing the Functional API,
subclassing the `Program` class, and using `Sequential` programs, you've gained a
solid foundation in creating modular and composable applications.
Now that we know how to program applications, you can learn how to control
the data flow in the next notebook.
### Key Takeaways
- **Functional API**: Allows you to chain modules to define the program's structure,
providing a clear and intuitive way to build applications.
- **Subclassing**: Offers flexibility and control by defining modules and implementing
the program's structure from scratch within a class.
- **Sequential Programs**: Simplifies the creation of linear workflows, making it easy
to stack single-input, single-output modules.
- **Modularity and Composability**: Enables the reuse of components, fostering creativity
and efficiency in application development.
"""
)
return
if __name__ == "__main__":
app.run()
|