lingo-ai 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ name: Release Pipeline
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Checkout repository
12
+ uses: actions/checkout@v4
13
+
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.12"
18
+
19
+ - name: Install uv
20
+ run: curl -LsSf https://astral.sh/uv/install.sh | sh
21
+
22
+ - name: Install dependencies
23
+ run: uv sync --all-extras --all-groups --no-editable
24
+
25
+ - name: Run format check
26
+ run: uv run black --check .
27
+
28
+ - name: Run tests with coverage
29
+ run: uv run pytest --cov=beaver --cov-report=xml --cov-report=term
30
+
31
+ - name: Upload coverage to Codecov
32
+ uses: codecov/codecov-action@v4
33
+ with:
34
+ files: ./coverage.xml
35
+ fail_ci_if_error: false
36
+
37
+ publish-pypi:
38
+ needs: test
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - name: Checkout repository
42
+ uses: actions/checkout@v4
43
+
44
+ - name: Set up Python
45
+ uses: actions/setup-python@v5
46
+ with:
47
+ python-version: "3.12"
48
+
49
+ - name: Install uv
50
+ run: curl -LsSf https://astral.sh/uv/install.sh | sh
51
+
52
+ - name: Build package
53
+ run: uv build
54
+
55
+ - name: Publish to PyPI
56
+ run: uv publish --token ${{ secrets.PYPI_TOKEN }}
@@ -0,0 +1,32 @@
1
+ name: Run Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout repository
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Install uv
23
+ run: curl -LsSf https://astral.sh/uv/install.sh | sh
24
+
25
+ - name: Install dependencies
26
+ run: uv sync --all-extras --all-groups --no-editable
27
+
28
+ - name: Run format check
29
+ run: uv run black --check .
30
+
31
+ - name: Run tests with coverage
32
+ run: uv run pytest --cov=beaver --cov-report=xml --cov-report=term
@@ -0,0 +1,217 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+ # Byte-compiled / optimized / DLL files
12
+ __pycache__/
13
+ *.py[codz]
14
+ *$py.class
15
+
16
+ # C extensions
17
+ *.so
18
+
19
+ # Distribution / packaging
20
+ .Python
21
+ build/
22
+ develop-eggs/
23
+ dist/
24
+ downloads/
25
+ eggs/
26
+ .eggs/
27
+ lib/
28
+ lib64/
29
+ parts/
30
+ sdist/
31
+ var/
32
+ wheels/
33
+ share/python-wheels/
34
+ *.egg-info/
35
+ .installed.cfg
36
+ *.egg
37
+ MANIFEST
38
+
39
+ # PyInstaller
40
+ # Usually these files are written by a python script from a template
41
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
42
+ *.manifest
43
+ *.spec
44
+
45
+ # Installer logs
46
+ pip-log.txt
47
+ pip-delete-this-directory.txt
48
+
49
+ # Unit test / coverage reports
50
+ htmlcov/
51
+ .tox/
52
+ .nox/
53
+ .coverage
54
+ .coverage.*
55
+ .cache
56
+ nosetests.xml
57
+ coverage.xml
58
+ *.cover
59
+ *.py.cover
60
+ .hypothesis/
61
+ .pytest_cache/
62
+ cover/
63
+
64
+ # Translations
65
+ *.mo
66
+ *.pot
67
+
68
+ # Django stuff:
69
+ *.log
70
+ local_settings.py
71
+ db.sqlite3
72
+ db.sqlite3-journal
73
+
74
+ # Flask stuff:
75
+ instance/
76
+ .webassets-cache
77
+
78
+ # Scrapy stuff:
79
+ .scrapy
80
+
81
+ # Sphinx documentation
82
+ docs/_build/
83
+
84
+ # PyBuilder
85
+ .pybuilder/
86
+ target/
87
+
88
+ # Jupyter Notebook
89
+ .ipynb_checkpoints
90
+
91
+ # IPython
92
+ profile_default/
93
+ ipython_config.py
94
+
95
+ # pyenv
96
+ # For a library or package, you might want to ignore these files since the code is
97
+ # intended to run in multiple environments; otherwise, check them in:
98
+ # .python-version
99
+
100
+ # pipenv
101
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
102
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
103
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
104
+ # install all needed dependencies.
105
+ #Pipfile.lock
106
+
107
+ # UV
108
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
109
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
110
+ # commonly ignored for libraries.
111
+ #uv.lock
112
+
113
+ # poetry
114
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
115
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
116
+ # commonly ignored for libraries.
117
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
118
+ #poetry.lock
119
+ #poetry.toml
120
+
121
+ # pdm
122
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
123
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
124
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
125
+ #pdm.lock
126
+ #pdm.toml
127
+ .pdm-python
128
+ .pdm-build/
129
+
130
+ # pixi
131
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
132
+ #pixi.lock
133
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
134
+ # in the .venv directory. It is recommended not to include this directory in version control.
135
+ .pixi
136
+
137
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
138
+ __pypackages__/
139
+
140
+ # Celery stuff
141
+ celerybeat-schedule
142
+ celerybeat.pid
143
+
144
+ # SageMath parsed files
145
+ *.sage.py
146
+
147
+ # Environments
148
+ .env
149
+ .envrc
150
+ .venv
151
+ env/
152
+ venv/
153
+ ENV/
154
+ env.bak/
155
+ venv.bak/
156
+
157
+ # Spyder project settings
158
+ .spyderproject
159
+ .spyproject
160
+
161
+ # Rope project settings
162
+ .ropeproject
163
+
164
+ # mkdocs documentation
165
+ /site
166
+
167
+ # mypy
168
+ .mypy_cache/
169
+ .dmypy.json
170
+ dmypy.json
171
+
172
+ # Pyre type checker
173
+ .pyre/
174
+
175
+ # pytype static type analyzer
176
+ .pytype/
177
+
178
+ # Cython debug symbols
179
+ cython_debug/
180
+
181
+ # PyCharm
182
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
183
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
184
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
185
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
186
+ #.idea/
187
+
188
+ # Abstra
189
+ # Abstra is an AI-powered process automation framework.
190
+ # Ignore directories containing user credentials, local state, and settings.
191
+ # Learn more at https://abstra.io/docs
192
+ .abstra/
193
+
194
+ # Visual Studio Code
195
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
196
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
197
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
198
+ # you could uncomment the following to ignore the entire vscode folder
199
+ # .vscode/
200
+
201
+ # Ruff stuff:
202
+ .ruff_cache/
203
+
204
+ # PyPI configuration file
205
+ .pypirc
206
+
207
+ # Cursor
208
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
209
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
210
+ # refer to https://docs.cursor.com/context/ignore-files
211
+ .cursorignore
212
+ .cursorindexingignore
213
+
214
+ # Marimo
215
+ marimo/_static/
216
+ marimo/_lsp/
217
+ __marimo__/
@@ -0,0 +1 @@
1
+ 3.12
lingo_ai-0.1.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Grupo de Inteligencia Artificial (GIA-UH)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: lingo-ai
3
+ Version: 0.1.2
4
+ Summary: Add your description here
5
+ Author-email: Alejandro Piad <apiad@apiad.net>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: openai>=2.7.2
9
+ Requires-Dist: pydantic>=2.12.4
10
+ Description-Content-Type: text/markdown
11
+
12
+ <p align="center"> <img src="https://github.com/user-attachments/assets/27a24307-cda0-4fa8-ba6c-9b5ca9b27efe" alt="lingo library logo" width="300"/> </p>
13
+
14
+ <p align="center"> <strong>A minimal, async-native, and unopinionated toolkit for modern LLM applications.</strong> </p>
15
+
16
+ ---
17
+
18
+ <!-- Project badges -->
19
+ ![PyPI - Version](https://img.shields.io/pypi/v/lingo)
20
+ ![PyPi - Python Version](https://img.shields.io/pypi/pyversions/lingo)
21
+ ![Github - Open Issues](https://img.shields.io/github/issues-raw/gia-uh/lingo)
22
+ ![PyPi - Downloads (Monthly)](https://img.shields.io/pypi/dm/lingo)
23
+ ![Github - Commits](https://img.shields.io/github/commit-activity/m/gia-uh/lingo)
24
+
25
+ -----
26
+
27
+ `lingo` provides a powerful, two-layered API for building, testing, and deploying complex LLM workflows with precision and clarity.
28
+
29
+ ## The Philosophy: A Dual-Layer API
30
+
31
+ `lingo` is built on the idea that developers need both a high-level, declarative way to _build_ workflows and a low-level, imperative way to _control_ them.
32
+
33
+ 1. **The High-Level `Flow` API**: For most applications, you'll use the fluent, chainable `Flow` API. This allows you to define complex, reusable conversation logic with branching, tool use, and subroutines in a declarative, readable way.
34
+
35
+ 2. **The Low-Level `Context` API**: For full, fine-grained control, `lingo` provides the `LLM` and `Context` classes. This imperative layer is perfect for building custom chatbot loops or implementing your own conversational state logic from scratch.
36
+
37
+
38
+ ## Installation
39
+
40
+ ```
41
+ pip install lingo
42
+ ```
43
+
44
+ _(Note: `lingo` is not yet on PyPI. This is the intended installation method.)_
45
+
46
+ ## Quickstart: Building a Declarative `Flow`
47
+
48
+ Let's build a simple assistant that can check the weather, but will first decide if the user is being polite.
49
+
50
+ ### 1. Define Tools & LLM
51
+
52
+ ```python
53
+ import asyncio
54
+ from lingo.llm import LLM, Message
55
+ from lingo.tools import tool
56
+ from lingo.flow import Flow, NoOp
57
+
58
+ llm = LLM(model="gpt-4o")
59
+
60
+ @tool
61
+ async def get_weather(location: str) -> str:
62
+ """Gets the current weather for a specified location."""
63
+ if "boston" in location.lower():
64
+ return "It's 75°F and sunny in Boston."
65
+ else:
66
+ return f"Weather for {location} is unknown."
67
+ ```
68
+
69
+ ### 2. Define Reusable Sub-Flows
70
+
71
+
72
+ ```python
73
+ # A sub-flow is just another 'Flow' instance.
74
+ polite_flow = Flow().system(
75
+ "Acknowledge the user's politeness before proceeding."
76
+ )
77
+ ```
78
+
79
+ ### 3. Define the Main Workflow
80
+
81
+
82
+ ```python
83
+ main_flow = (
84
+ Flow()
85
+ # Step 1: Add a system message to the context
86
+ .system("You are a helpful assistant. You can check the weather.")
87
+
88
+ # Step 2: Use an LLM to make a True/False decision
89
+ .decide(
90
+ prompt="Is the user being polite (e.g., asking 'please')?",
91
+ on_true=polite_flow, # Run the sub-flow if True
92
+ on_false=NoOp() # Do nothing if False
93
+ )
94
+
95
+ # Step 3: Equip and invoke a tool from the available list
96
+ .invoke(get_weather)
97
+
98
+ # Step 4: Generate a final reply using the tool's output
99
+ .reply()
100
+ )
101
+ ```
102
+
103
+ ### 4. Execute the Flow
104
+
105
+
106
+ ```python
107
+ async def main():
108
+ # Example 1: The "polite" branch
109
+ user_query_1 = "Could you please tell me the weather in Boston?"
110
+
111
+ # .execute() runs the full pipeline on the initial messages
112
+ final_context = await main_flow.run(llm, [Message.user(user_query_1)])
113
+
114
+ print(f"User: {user_query_1}")
115
+ print(f"Assistant: {final_context.messages[-1].content}")
116
+ # Assistant: It's 75°F and sunny in Boston. I'm happy to help!
117
+
118
+ print("\n" + "-"*20 + "\n")
119
+
120
+ # Example 2: The "impolite" branch
121
+ user_query_2 = "boston weather now"
122
+ final_context_2 = await main_flow.run(llm, [Message.user(user_query_2)])
123
+
124
+ print(f"User: {user_query_2}")
125
+ print(f"Assistant: {final_context_2.messages[-1].content}")
126
+ # Assistant: It's 75°F and sunny in Boston.
127
+
128
+ if __name__ == "__main__":
129
+ asyncio.run(main())
130
+ ```
131
+
132
+ ## The Two APIs
133
+
134
+ `lingo` gives you the flexibility to choose the right level of abstraction.
135
+
136
+ ### 1. High-Level API: `Flow`
137
+
138
+ The `Flow` class provides a fluent, chainable interface for declaratively building reusable workflows. You define the _steps_ of the conversation, and `lingo` handles the execution.
139
+
140
+ ```python
141
+ # A flow is a composable, reusable blueprint
142
+ weather_flow = (
143
+ Flow()
144
+ .system("You only answer with the weather.")
145
+ .invoke(get_weather)
146
+ .reply()
147
+ )
148
+
149
+ # You can nest flows inside other flows
150
+ main_flow = (
151
+ Flow()
152
+ .choose(
153
+ prompt="Is the user asking about weather or stocks?",
154
+ choices={
155
+ "weather": weather_flow,
156
+ "stocks": stock_flow,
157
+ }
158
+ )
159
+ )
160
+ ```
161
+
162
+ ### 2. Low-Level API: `LLM` & `Context`
163
+
164
+ For maximum control, you can use the imperative API. The `Context` object holds the message history, and you call its methods (`.reply()`, `.decide()`, `.invoke()`) directly. This is ideal for building custom chatbot loops.
165
+
166
+ ```python
167
+ # Manually building a conversation turn by turn
168
+ llm = LLM(model="gpt-4o")
169
+ context = Context(llm, [Message.system("You are a chatbot.")])
170
+
171
+ while True:
172
+ user_input = input("You: ")
173
+ context.add(Message.user(user_input))
174
+
175
+ # Call the LLM with the current context
176
+ response = await context.reply()
177
+
178
+ context.add(response)
179
+ print(f"Bot: {response.content}")
180
+ ```
181
+
182
+ ## Contributing
183
+
184
+ Contributions are welcome! `lingo` is an open-source project, and we'd love your help in making it better. Please feel free to open an issue or submit a pull request.
185
+
186
+ ## License
187
+
188
+ `lingo` is licensed under the **MIT License**. See the `LICENSE` file for details.
@@ -0,0 +1,177 @@
1
+ <p align="center"> <img src="https://github.com/user-attachments/assets/27a24307-cda0-4fa8-ba6c-9b5ca9b27efe" alt="lingo library logo" width="300"/> </p>
2
+
3
+ <p align="center"> <strong>A minimal, async-native, and unopinionated toolkit for modern LLM applications.</strong> </p>
4
+
5
+ ---
6
+
7
+ <!-- Project badges -->
8
+ ![PyPI - Version](https://img.shields.io/pypi/v/lingo)
9
+ ![PyPi - Python Version](https://img.shields.io/pypi/pyversions/lingo)
10
+ ![Github - Open Issues](https://img.shields.io/github/issues-raw/gia-uh/lingo)
11
+ ![PyPi - Downloads (Monthly)](https://img.shields.io/pypi/dm/lingo)
12
+ ![Github - Commits](https://img.shields.io/github/commit-activity/m/gia-uh/lingo)
13
+
14
+ -----
15
+
16
+ `lingo` provides a powerful, two-layered API for building, testing, and deploying complex LLM workflows with precision and clarity.
17
+
18
+ ## The Philosophy: A Dual-Layer API
19
+
20
+ `lingo` is built on the idea that developers need both a high-level, declarative way to _build_ workflows and a low-level, imperative way to _control_ them.
21
+
22
+ 1. **The High-Level `Flow` API**: For most applications, you'll use the fluent, chainable `Flow` API. This allows you to define complex, reusable conversation logic with branching, tool use, and subroutines in a declarative, readable way.
23
+
24
+ 2. **The Low-Level `Context` API**: For full, fine-grained control, `lingo` provides the `LLM` and `Context` classes. This imperative layer is perfect for building custom chatbot loops or implementing your own conversational state logic from scratch.
25
+
26
+
27
+ ## Installation
28
+
29
+ ```
30
+ pip install lingo
31
+ ```
32
+
33
+ _(Note: `lingo` is not yet on PyPI. This is the intended installation method.)_
34
+
35
+ ## Quickstart: Building a Declarative `Flow`
36
+
37
+ Let's build a simple assistant that can check the weather, but will first decide if the user is being polite.
38
+
39
+ ### 1. Define Tools & LLM
40
+
41
+ ```python
42
+ import asyncio
43
+ from lingo.llm import LLM, Message
44
+ from lingo.tools import tool
45
+ from lingo.flow import Flow, NoOp
46
+
47
+ llm = LLM(model="gpt-4o")
48
+
49
+ @tool
50
+ async def get_weather(location: str) -> str:
51
+ """Gets the current weather for a specified location."""
52
+ if "boston" in location.lower():
53
+ return "It's 75°F and sunny in Boston."
54
+ else:
55
+ return f"Weather for {location} is unknown."
56
+ ```
57
+
58
+ ### 2. Define Reusable Sub-Flows
59
+
60
+
61
+ ```python
62
+ # A sub-flow is just another 'Flow' instance.
63
+ polite_flow = Flow().system(
64
+ "Acknowledge the user's politeness before proceeding."
65
+ )
66
+ ```
67
+
68
+ ### 3. Define the Main Workflow
69
+
70
+
71
+ ```python
72
+ main_flow = (
73
+ Flow()
74
+ # Step 1: Add a system message to the context
75
+ .system("You are a helpful assistant. You can check the weather.")
76
+
77
+ # Step 2: Use an LLM to make a True/False decision
78
+ .decide(
79
+ prompt="Is the user being polite (e.g., asking 'please')?",
80
+ on_true=polite_flow, # Run the sub-flow if True
81
+ on_false=NoOp() # Do nothing if False
82
+ )
83
+
84
+ # Step 3: Equip and invoke a tool from the available list
85
+ .invoke(get_weather)
86
+
87
+ # Step 4: Generate a final reply using the tool's output
88
+ .reply()
89
+ )
90
+ ```
91
+
92
+ ### 4. Execute the Flow
93
+
94
+
95
+ ```python
96
+ async def main():
97
+ # Example 1: The "polite" branch
98
+ user_query_1 = "Could you please tell me the weather in Boston?"
99
+
100
+ # .execute() runs the full pipeline on the initial messages
101
+ final_context = await main_flow.run(llm, [Message.user(user_query_1)])
102
+
103
+ print(f"User: {user_query_1}")
104
+ print(f"Assistant: {final_context.messages[-1].content}")
105
+ # Assistant: It's 75°F and sunny in Boston. I'm happy to help!
106
+
107
+ print("\n" + "-"*20 + "\n")
108
+
109
+ # Example 2: The "impolite" branch
110
+ user_query_2 = "boston weather now"
111
+ final_context_2 = await main_flow.run(llm, [Message.user(user_query_2)])
112
+
113
+ print(f"User: {user_query_2}")
114
+ print(f"Assistant: {final_context_2.messages[-1].content}")
115
+ # Assistant: It's 75°F and sunny in Boston.
116
+
117
+ if __name__ == "__main__":
118
+ asyncio.run(main())
119
+ ```
120
+
121
+ ## The Two APIs
122
+
123
+ `lingo` gives you the flexibility to choose the right level of abstraction.
124
+
125
+ ### 1. High-Level API: `Flow`
126
+
127
+ The `Flow` class provides a fluent, chainable interface for declaratively building reusable workflows. You define the _steps_ of the conversation, and `lingo` handles the execution.
128
+
129
+ ```python
130
+ # A flow is a composable, reusable blueprint
131
+ weather_flow = (
132
+ Flow()
133
+ .system("You only answer with the weather.")
134
+ .invoke(get_weather)
135
+ .reply()
136
+ )
137
+
138
+ # You can nest flows inside other flows
139
+ main_flow = (
140
+ Flow()
141
+ .choose(
142
+ prompt="Is the user asking about weather or stocks?",
143
+ choices={
144
+ "weather": weather_flow,
145
+ "stocks": stock_flow,
146
+ }
147
+ )
148
+ )
149
+ ```
150
+
151
+ ### 2. Low-Level API: `LLM` & `Context`
152
+
153
+ For maximum control, you can use the imperative API. The `Context` object holds the message history, and you call its methods (`.reply()`, `.decide()`, `.invoke()`) directly. This is ideal for building custom chatbot loops.
154
+
155
+ ```python
156
+ # Manually building a conversation turn by turn
157
+ llm = LLM(model="gpt-4o")
158
+ context = Context(llm, [Message.system("You are a chatbot.")])
159
+
160
+ while True:
161
+ user_input = input("You: ")
162
+ context.add(Message.user(user_input))
163
+
164
+ # Call the LLM with the current context
165
+ response = await context.reply()
166
+
167
+ context.add(response)
168
+ print(f"Bot: {response.content}")
169
+ ```
170
+
171
+ ## Contributing
172
+
173
+ Contributions are welcome! `lingo` is an open-source project, and we'd love your help in making it better. Please feel free to open an issue or submit a pull request.
174
+
175
+ ## License
176
+
177
+ `lingo` is licensed under the **MIT License**. See the `LICENSE` file for details.
File without changes