functai 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,35 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ *.egg
9
+
10
+ # Virtual environments
11
+ .venv
12
+ venv/
13
+ env/
14
+ ENV/
15
+
16
+ # Testing
17
+ .coverage
18
+ htmlcov/
19
+ .pytest_cache/
20
+ .tox/
21
+
22
+ # IDE
23
+ .vscode/
24
+ .idea/
25
+ *.swp
26
+ *.swo
27
+ *~
28
+
29
+ # OS
30
+ .DS_Store
31
+ Thumbs.db
32
+
33
+ # Package files
34
+ *.tar.gz
35
+ *.whl
functai-0.1.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 FunctAI Contributors
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.
functai-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,280 @@
1
+ Metadata-Version: 2.4
2
+ Name: functai
3
+ Version: 0.1.2
4
+ Summary: DSPy-powered function decorators for AI-enhanced programming
5
+ Project-URL: Homepage, https://github.com/maximerivest/functai
6
+ Project-URL: Bug Tracker, https://github.com/maximerivest/functai/issues
7
+ Project-URL: Documentation, https://github.com/maximerivest/functai#readme
8
+ Project-URL: Source, https://github.com/maximerivest/functai
9
+ Author-email: Maxime Rivest <maxime.rivest@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: ai,artificial-intelligence,decorators,dspy,function-decorators,llm,machine-learning,prompt-engineering
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: dspy>=3.0.2
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
28
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
29
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # FunctAI
33
+
34
+ DSPy-powered function decorators that turn typed Python into single-call LLM programs.
35
+
36
+ Highlights
37
+ - Single-call `@magic` functions with `step()`/`final()` markers
38
+ - Per-function adapters (`adapter="json" | "chat" | dspy.Adapter`)
39
+ - Pass LM by string (`lm="gpt-4.1"`) or LM instance; DSPy resolves providers
40
+ - Works with DSPy modules: `Predict`, `ChainOfThought`, `ReAct` (with tools)
41
+ - Structured outputs: plain types, lists/dicts, dataclasses, namedtuples
42
+ - Batch with `parallel(...)`, compile with `optimize(...)`
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ # Using uv (recommended)
48
+ uv pip install -e .
49
+
50
+ # Or with pip
51
+ pip install -e .
52
+ ```
53
+
54
+ ## Configure LM
55
+
56
+ You can let FunctAI/DSPy resolve the provider by passing a model string:
57
+
58
+ ```python
59
+ @magic(lm="gpt-4.1")
60
+ def echo(text: str) -> str:
61
+ "It returns the input text."
62
+ ...
63
+ ```
64
+
65
+ This is equivalent to setting `mod.lm = dspy.LM("gpt-4.1")`. You can also pass a provider-specific LM:
66
+
67
+ ```python
68
+ import dspy
69
+ @magic(lm=dspy.LM(model="gpt-4.1"))
70
+ def echo(text: str) -> str: ...
71
+ ```
72
+
73
+ Alternatively, configure globally:
74
+
75
+ ```python
76
+ import dspy
77
+ dspy.settings.configure(lm=dspy.LM("gpt-4.1"))
78
+ ```
79
+
80
+ Ensure provider environment variables are set (e.g., `OPENAI_API_KEY`).
81
+
82
+ ## Quick Start
83
+
84
+ ```python
85
+ from functai import magic
86
+
87
+ @magic(adapter="json")
88
+ def classify(text: str) -> str:
89
+ """Return 'positive' or 'negative'."""
90
+ ...
91
+
92
+ result = classify("This library is amazing!") # → "positive"
93
+ ```
94
+
95
+ ## Markers and Lazy Outputs
96
+
97
+ Use `step()` and `final()` markers to define intermediate and final outputs. FunctAI builds a DSPy signature from your function and makes a single LLM call when a marked value is first used.
98
+
99
+ ```python
100
+ from functai import magic, step, final
101
+ from typing import List
102
+
103
+ @magic(adapter="json", lm="gpt-4.1")
104
+ def analyze(text: str) -> dict:
105
+ _sentiment: str = step(desc="Determine sentiment")
106
+ _keywords: List[str] = step(desc="Extract keywords")
107
+ summary: dict = final(desc="Combine analysis")
108
+ return summary
109
+
110
+ res = analyze("FunctAI makes AI programming fun and easy!")
111
+ ```
112
+
113
+ Lazy proxies materialize on first use. You can:
114
+ - Access directly: `str(res)` or `dict(res)`
115
+ - Force materialize: `res.value`
116
+ - Get raw DSPy output: `analyze(..., _prediction=True)` (returns `dspy.Prediction`)
117
+
118
+ Tip: Return eager values by doing `return summary.value`.
119
+
120
+ Unannotated markers are supported too:
121
+
122
+ ```python
123
+ @magic(lm="gpt-4.1")
124
+ def fn(x: str) -> str:
125
+ tmp = step(desc="Intermediate") # type Any
126
+ out = final(desc="Final") # type Any
127
+ return out
128
+ ```
129
+
130
+ You can also return `final(...)` directly without naming a variable. In that case, the output is named `result` by default and typed from your function’s return annotation:
131
+
132
+ ```python
133
+ from functai import magic, step, final
134
+ from typing import List
135
+
136
+ @magic(adapter="json", lm="gpt-4.1")
137
+ def analyze(text: str) -> dict:
138
+ _sentiment: str = step("Determine sentiment")
139
+ _keywords: List[str] = step("Extract keywords")
140
+ return final("Combine analysis") # final output name defaults to 'result'
141
+
142
+ res = analyze("FunctAI makes AI programming fun and easy!")
143
+ ```
144
+
145
+ ## Structured Outputs
146
+
147
+ You can return dataclasses or namedtuples. Fields become model outputs and are reconstructed after the call.
148
+
149
+ ```python
150
+ from dataclasses import dataclass
151
+ from typing import List
152
+ from functai import magic, step, final
153
+
154
+ @dataclass
155
+ class Analysis:
156
+ sentiment: str
157
+ confidence: float
158
+ keywords: List[str]
159
+
160
+ @magic(adapter="json", lm="gpt-4.1")
161
+ def analyze_text(text: str) -> Analysis:
162
+ _sentiment: str = step("Determine sentiment")
163
+ _confidence: float = step("Confidence score between 0 and 1")
164
+ _keywords: List[str] = step("Important keywords")
165
+ result: Analysis = final("Complete analysis")
166
+ return result
167
+ ```
168
+
169
+ ## Choosing Modules and Adapters
170
+
171
+ `module` accepts a string (`"predict"`, `"cot"`, `"react"`), a `dspy.Module` subclass, or an instance. All `module_kwargs` are forwarded to the module constructor. For ReAct, pass tools via `tools=[...]` or `module_kwargs={"tools": [...]}`.
172
+
173
+ ```python
174
+ import dspy
175
+ from functai import magic, final
176
+
177
+ def get_weather(city: str) -> str:
178
+ return "sunny"
179
+
180
+ @magic(lm="gpt-4.1", module=dspy.ReAct, tools=[get_weather])
181
+ def agent(question: str) -> str:
182
+ answer: str = final("Answer the question")
183
+ return answer
184
+
185
+ @magic(module="cot", lm="gpt-4.1")
186
+ def derive(text: str) -> str:
187
+ proof: str = final("Show your reasoning")
188
+ return proof
189
+ ```
190
+
191
+ ReAct notes
192
+ - ReAct builds two subprograms: an agent (react) and an extractor (extract). Step/Final fields are produced by the extract stage.
193
+ - If a module only returns `result`, FunctAI maps your `final()` to that `result` automatically.
194
+
195
+ Adapters
196
+ - `adapter="json"` → `dspy.JSONAdapter()`
197
+ - `adapter="chat"` → `dspy.ChatAdapter()`
198
+ - Custom adapters are supported (class or instance). A two-step adapter requires `adapter_kwargs`.
199
+
200
+ ## Batch and Optimize
201
+
202
+ Parallel batch over the underlying module:
203
+
204
+ ```python
205
+ from functai import parallel
206
+
207
+ rows = parallel(classify, inputs=[{"text": "great"}, {"text": "bad"}])
208
+ ```
209
+
210
+ Compile with an optimizer (e.g., BootstrapFewShot). The per-function adapter and LM are preserved:
211
+
212
+ ```python
213
+ from functai import optimize
214
+
215
+ trainset = [("I love it", "positive"), ("Terrible UX", "negative")]
216
+ compiled_classify = optimize(classify, trainset=trainset)
217
+ compiled_classify("So good!")
218
+ ```
219
+
220
+ ## Prediction Mode
221
+
222
+ Every `@magic` function accepts `_prediction=True` to return the raw `dspy.Prediction`:
223
+
224
+ ```python
225
+ pred = agent("What is the surprise?", _prediction=True)
226
+ print(pred.result) # the final answer
227
+ print(pred.reasoning) # when available (e.g., ReAct extract stage)
228
+ print(pred.trajectory) # full tool-use trace for ReAct
229
+ ```
230
+
231
+ ## Inspect & Preview Prompts
232
+
233
+ Preview the adapter-formatted messages without making a call:
234
+
235
+ ```python
236
+ from functai import format_prompt
237
+ preview = format_prompt(analyze, text="Hello world")
238
+ print(preview["render"]) # nice human-readable view
239
+ print(preview["messages"]) # list of {role, content}
240
+ print(preview["demos"]) # extracted demos (if any)
241
+ ```
242
+
243
+ After a call, view the provider-level history via DSPy:
244
+
245
+ ```python
246
+ from functai import inspect_history_text
247
+ print(inspect_history_text()) # captures dspy.inspect_history() output
248
+ ```
249
+
250
+ ## Linting Tips
251
+
252
+ Some linters (e.g., Ruff F841) flag variables assigned but not used. This is common with `step()` markers that are consumed by the LLM, not Python. Two options:
253
+
254
+ 1) Prefix with underscores (sanitized when building the signature)
255
+
256
+ ```python
257
+ _sentiment: str = step("Determine sentiment")
258
+ ```
259
+
260
+ 2) Mark as used with `use(...)`
261
+
262
+ ```python
263
+ from functai import use
264
+ sentiment: str = step("Determine sentiment")
265
+ use(sentiment)
266
+ ```
267
+
268
+ ## Development
269
+
270
+ ```bash
271
+ # Install with dev dependencies
272
+ uv pip install -e ".[dev]"
273
+
274
+ # Run tests
275
+ uv run pytest
276
+
277
+ # Format and lint
278
+ uv run ruff format .
279
+ uv run ruff check .
280
+ ```
@@ -0,0 +1,249 @@
1
+ # FunctAI
2
+
3
+ DSPy-powered function decorators that turn typed Python into single-call LLM programs.
4
+
5
+ Highlights
6
+ - Single-call `@magic` functions with `step()`/`final()` markers
7
+ - Per-function adapters (`adapter="json" | "chat" | dspy.Adapter`)
8
+ - Pass LM by string (`lm="gpt-4.1"`) or LM instance; DSPy resolves providers
9
+ - Works with DSPy modules: `Predict`, `ChainOfThought`, `ReAct` (with tools)
10
+ - Structured outputs: plain types, lists/dicts, dataclasses, namedtuples
11
+ - Batch with `parallel(...)`, compile with `optimize(...)`
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ # Using uv (recommended)
17
+ uv pip install -e .
18
+
19
+ # Or with pip
20
+ pip install -e .
21
+ ```
22
+
23
+ ## Configure LM
24
+
25
+ You can let FunctAI/DSPy resolve the provider by passing a model string:
26
+
27
+ ```python
28
+ @magic(lm="gpt-4.1")
29
+ def echo(text: str) -> str:
30
+ "It returns the input text."
31
+ ...
32
+ ```
33
+
34
+ This is equivalent to setting `mod.lm = dspy.LM("gpt-4.1")`. You can also pass a provider-specific LM:
35
+
36
+ ```python
37
+ import dspy
38
+ @magic(lm=dspy.LM(model="gpt-4.1"))
39
+ def echo(text: str) -> str: ...
40
+ ```
41
+
42
+ Alternatively, configure globally:
43
+
44
+ ```python
45
+ import dspy
46
+ dspy.settings.configure(lm=dspy.LM("gpt-4.1"))
47
+ ```
48
+
49
+ Ensure provider environment variables are set (e.g., `OPENAI_API_KEY`).
50
+
51
+ ## Quick Start
52
+
53
+ ```python
54
+ from functai import magic
55
+
56
+ @magic(adapter="json")
57
+ def classify(text: str) -> str:
58
+ """Return 'positive' or 'negative'."""
59
+ ...
60
+
61
+ result = classify("This library is amazing!") # → "positive"
62
+ ```
63
+
64
+ ## Markers and Lazy Outputs
65
+
66
+ Use `step()` and `final()` markers to define intermediate and final outputs. FunctAI builds a DSPy signature from your function and makes a single LLM call when a marked value is first used.
67
+
68
+ ```python
69
+ from functai import magic, step, final
70
+ from typing import List
71
+
72
+ @magic(adapter="json", lm="gpt-4.1")
73
+ def analyze(text: str) -> dict:
74
+ _sentiment: str = step(desc="Determine sentiment")
75
+ _keywords: List[str] = step(desc="Extract keywords")
76
+ summary: dict = final(desc="Combine analysis")
77
+ return summary
78
+
79
+ res = analyze("FunctAI makes AI programming fun and easy!")
80
+ ```
81
+
82
+ Lazy proxies materialize on first use. You can:
83
+ - Access directly: `str(res)` or `dict(res)`
84
+ - Force materialize: `res.value`
85
+ - Get raw DSPy output: `analyze(..., _prediction=True)` (returns `dspy.Prediction`)
86
+
87
+ Tip: Return eager values by doing `return summary.value`.
88
+
89
+ Unannotated markers are supported too:
90
+
91
+ ```python
92
+ @magic(lm="gpt-4.1")
93
+ def fn(x: str) -> str:
94
+ tmp = step(desc="Intermediate") # type Any
95
+ out = final(desc="Final") # type Any
96
+ return out
97
+ ```
98
+
99
+ You can also return `final(...)` directly without naming a variable. In that case, the output is named `result` by default and typed from your function’s return annotation:
100
+
101
+ ```python
102
+ from functai import magic, step, final
103
+ from typing import List
104
+
105
+ @magic(adapter="json", lm="gpt-4.1")
106
+ def analyze(text: str) -> dict:
107
+ _sentiment: str = step("Determine sentiment")
108
+ _keywords: List[str] = step("Extract keywords")
109
+ return final("Combine analysis") # final output name defaults to 'result'
110
+
111
+ res = analyze("FunctAI makes AI programming fun and easy!")
112
+ ```
113
+
114
+ ## Structured Outputs
115
+
116
+ You can return dataclasses or namedtuples. Fields become model outputs and are reconstructed after the call.
117
+
118
+ ```python
119
+ from dataclasses import dataclass
120
+ from typing import List
121
+ from functai import magic, step, final
122
+
123
+ @dataclass
124
+ class Analysis:
125
+ sentiment: str
126
+ confidence: float
127
+ keywords: List[str]
128
+
129
+ @magic(adapter="json", lm="gpt-4.1")
130
+ def analyze_text(text: str) -> Analysis:
131
+ _sentiment: str = step("Determine sentiment")
132
+ _confidence: float = step("Confidence score between 0 and 1")
133
+ _keywords: List[str] = step("Important keywords")
134
+ result: Analysis = final("Complete analysis")
135
+ return result
136
+ ```
137
+
138
+ ## Choosing Modules and Adapters
139
+
140
+ `module` accepts a string (`"predict"`, `"cot"`, `"react"`), a `dspy.Module` subclass, or an instance. All `module_kwargs` are forwarded to the module constructor. For ReAct, pass tools via `tools=[...]` or `module_kwargs={"tools": [...]}`.
141
+
142
+ ```python
143
+ import dspy
144
+ from functai import magic, final
145
+
146
+ def get_weather(city: str) -> str:
147
+ return "sunny"
148
+
149
+ @magic(lm="gpt-4.1", module=dspy.ReAct, tools=[get_weather])
150
+ def agent(question: str) -> str:
151
+ answer: str = final("Answer the question")
152
+ return answer
153
+
154
+ @magic(module="cot", lm="gpt-4.1")
155
+ def derive(text: str) -> str:
156
+ proof: str = final("Show your reasoning")
157
+ return proof
158
+ ```
159
+
160
+ ReAct notes
161
+ - ReAct builds two subprograms: an agent (react) and an extractor (extract). Step/Final fields are produced by the extract stage.
162
+ - If a module only returns `result`, FunctAI maps your `final()` to that `result` automatically.
163
+
164
+ Adapters
165
+ - `adapter="json"` → `dspy.JSONAdapter()`
166
+ - `adapter="chat"` → `dspy.ChatAdapter()`
167
+ - Custom adapters are supported (class or instance). A two-step adapter requires `adapter_kwargs`.
168
+
169
+ ## Batch and Optimize
170
+
171
+ Parallel batch over the underlying module:
172
+
173
+ ```python
174
+ from functai import parallel
175
+
176
+ rows = parallel(classify, inputs=[{"text": "great"}, {"text": "bad"}])
177
+ ```
178
+
179
+ Compile with an optimizer (e.g., BootstrapFewShot). The per-function adapter and LM are preserved:
180
+
181
+ ```python
182
+ from functai import optimize
183
+
184
+ trainset = [("I love it", "positive"), ("Terrible UX", "negative")]
185
+ compiled_classify = optimize(classify, trainset=trainset)
186
+ compiled_classify("So good!")
187
+ ```
188
+
189
+ ## Prediction Mode
190
+
191
+ Every `@magic` function accepts `_prediction=True` to return the raw `dspy.Prediction`:
192
+
193
+ ```python
194
+ pred = agent("What is the surprise?", _prediction=True)
195
+ print(pred.result) # the final answer
196
+ print(pred.reasoning) # when available (e.g., ReAct extract stage)
197
+ print(pred.trajectory) # full tool-use trace for ReAct
198
+ ```
199
+
200
+ ## Inspect & Preview Prompts
201
+
202
+ Preview the adapter-formatted messages without making a call:
203
+
204
+ ```python
205
+ from functai import format_prompt
206
+ preview = format_prompt(analyze, text="Hello world")
207
+ print(preview["render"]) # nice human-readable view
208
+ print(preview["messages"]) # list of {role, content}
209
+ print(preview["demos"]) # extracted demos (if any)
210
+ ```
211
+
212
+ After a call, view the provider-level history via DSPy:
213
+
214
+ ```python
215
+ from functai import inspect_history_text
216
+ print(inspect_history_text()) # captures dspy.inspect_history() output
217
+ ```
218
+
219
+ ## Linting Tips
220
+
221
+ Some linters (e.g., Ruff F841) flag variables assigned but not used. This is common with `step()` markers that are consumed by the LLM, not Python. Two options:
222
+
223
+ 1) Prefix with underscores (sanitized when building the signature)
224
+
225
+ ```python
226
+ _sentiment: str = step("Determine sentiment")
227
+ ```
228
+
229
+ 2) Mark as used with `use(...)`
230
+
231
+ ```python
232
+ from functai import use
233
+ sentiment: str = step("Determine sentiment")
234
+ use(sentiment)
235
+ ```
236
+
237
+ ## Development
238
+
239
+ ```bash
240
+ # Install with dev dependencies
241
+ uv pip install -e ".[dev]"
242
+
243
+ # Run tests
244
+ uv run pytest
245
+
246
+ # Format and lint
247
+ uv run ruff format .
248
+ uv run ruff check .
249
+ ```
@@ -0,0 +1,30 @@
1
+ """
2
+ FunctAI - DSPy-powered function decorators for AI-enhanced programming.
3
+
4
+ A library that seamlessly integrates AI capabilities into Python functions
5
+ using DSPy's powerful prompting and optimization framework.
6
+ """
7
+
8
+ from functai.core import (
9
+ magic,
10
+ step,
11
+ final,
12
+ optimize,
13
+ parallel,
14
+ use,
15
+ format_prompt,
16
+ inspect_history_text,
17
+ )
18
+
19
+ __version__ = "0.1.2"
20
+
21
+ __all__ = [
22
+ "magic",
23
+ "step",
24
+ "final",
25
+ "optimize",
26
+ "parallel",
27
+ "use",
28
+ "format_prompt",
29
+ "inspect_history_text",
30
+ ]