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.
- functai-0.1.2/.gitignore +35 -0
- functai-0.1.2/LICENSE +21 -0
- functai-0.1.2/PKG-INFO +280 -0
- functai-0.1.2/README.md +249 -0
- functai-0.1.2/functai/__init__.py +30 -0
- functai-0.1.2/functai/core.py +1123 -0
- functai-0.1.2/pyproject.toml +66 -0
functai-0.1.2/.gitignore
ADDED
|
@@ -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
|
+
```
|
functai-0.1.2/README.md
ADDED
|
@@ -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
|
+
]
|