afsar 1.0__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.
- afsar-1.0/.gitignore +220 -0
- afsar-1.0/.pre-commit-config.yaml +44 -0
- afsar-1.0/PKG-INFO +149 -0
- afsar-1.0/README.md +134 -0
- afsar-1.0/agent/agy_design.md +264 -0
- afsar-1.0/agent/code_examples.md +19 -0
- afsar-1.0/agent/package.md +44 -0
- afsar-1.0/agent/pytools.md +13 -0
- afsar-1.0/examples/__init__.py +1 -0
- afsar-1.0/examples/teach_literals.py +73 -0
- afsar-1.0/ops/check_code_limits.py +118 -0
- afsar-1.0/pyproject.toml +46 -0
- afsar-1.0/src/afsar/__init__.py +27 -0
- afsar-1.0/src/afsar/adapters/__init__.py +15 -0
- afsar-1.0/src/afsar/adapters/agy.py +87 -0
- afsar-1.0/src/afsar/adapters/base.py +25 -0
- afsar-1.0/src/afsar/adapters/claude.py +103 -0
- afsar-1.0/src/afsar/adapters/gemini.py +86 -0
- afsar-1.0/src/afsar/adapters/openai.py +89 -0
- afsar-1.0/src/afsar/core.py +164 -0
- afsar-1.0/src/afsar/exceptions.py +25 -0
- afsar-1.0/src/afsar/resources/afsar.png +0 -0
- afsar-1.0/src/afsar/schema.py +111 -0
- afsar-1.0/tests/test_adapters.py +76 -0
- afsar-1.0/tests/test_examples.py +49 -0
- afsar-1.0/tests/test_lifecycle.py +95 -0
- afsar-1.0/tests/test_schema.py +62 -0
afsar-1.0/.gitignore
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
*.lcov
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
cover/
|
|
54
|
+
|
|
55
|
+
# Translations
|
|
56
|
+
*.mo
|
|
57
|
+
*.pot
|
|
58
|
+
|
|
59
|
+
# Django stuff:
|
|
60
|
+
*.log
|
|
61
|
+
local_settings.py
|
|
62
|
+
db.sqlite3
|
|
63
|
+
db.sqlite3-journal
|
|
64
|
+
|
|
65
|
+
# Flask stuff:
|
|
66
|
+
instance/
|
|
67
|
+
.webassets-cache
|
|
68
|
+
|
|
69
|
+
# Scrapy stuff:
|
|
70
|
+
.scrapy
|
|
71
|
+
|
|
72
|
+
# Sphinx documentation
|
|
73
|
+
docs/_build/
|
|
74
|
+
|
|
75
|
+
# PyBuilder
|
|
76
|
+
.pybuilder/
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
88
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
89
|
+
# .python-version
|
|
90
|
+
|
|
91
|
+
# pipenv
|
|
92
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
93
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
94
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
95
|
+
# install all needed dependencies.
|
|
96
|
+
# Pipfile.lock
|
|
97
|
+
|
|
98
|
+
# uv
|
|
99
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
100
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
101
|
+
# commonly ignored for libraries.
|
|
102
|
+
# uv.lock
|
|
103
|
+
|
|
104
|
+
# poetry
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
106
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
107
|
+
# commonly ignored for libraries.
|
|
108
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
109
|
+
# poetry.lock
|
|
110
|
+
# poetry.toml
|
|
111
|
+
|
|
112
|
+
# pdm
|
|
113
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
114
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
115
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
116
|
+
# pdm.lock
|
|
117
|
+
# pdm.toml
|
|
118
|
+
.pdm-python
|
|
119
|
+
.pdm-build/
|
|
120
|
+
|
|
121
|
+
# pixi
|
|
122
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
123
|
+
# pixi.lock
|
|
124
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
125
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
126
|
+
.pixi/*
|
|
127
|
+
!.pixi/config.toml
|
|
128
|
+
|
|
129
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
130
|
+
__pypackages__/
|
|
131
|
+
|
|
132
|
+
# Celery stuff
|
|
133
|
+
celerybeat-schedule*
|
|
134
|
+
celerybeat.pid
|
|
135
|
+
|
|
136
|
+
# Redis
|
|
137
|
+
*.rdb
|
|
138
|
+
*.aof
|
|
139
|
+
*.pid
|
|
140
|
+
|
|
141
|
+
# RabbitMQ
|
|
142
|
+
mnesia/
|
|
143
|
+
rabbitmq/
|
|
144
|
+
rabbitmq-data/
|
|
145
|
+
|
|
146
|
+
# ActiveMQ
|
|
147
|
+
activemq-data/
|
|
148
|
+
|
|
149
|
+
# SageMath parsed files
|
|
150
|
+
*.sage.py
|
|
151
|
+
|
|
152
|
+
# Environments
|
|
153
|
+
.env
|
|
154
|
+
.envrc
|
|
155
|
+
.venv
|
|
156
|
+
env/
|
|
157
|
+
venv/
|
|
158
|
+
ENV/
|
|
159
|
+
env.bak/
|
|
160
|
+
venv.bak/
|
|
161
|
+
|
|
162
|
+
# Spyder project settings
|
|
163
|
+
.spyderproject
|
|
164
|
+
.spyproject
|
|
165
|
+
|
|
166
|
+
# Rope project settings
|
|
167
|
+
.ropeproject
|
|
168
|
+
|
|
169
|
+
# mkdocs/Zensical documentation
|
|
170
|
+
/site
|
|
171
|
+
|
|
172
|
+
# mypy
|
|
173
|
+
.mypy_cache/
|
|
174
|
+
.dmypy.json
|
|
175
|
+
dmypy.json
|
|
176
|
+
|
|
177
|
+
# Pyre type checker
|
|
178
|
+
.pyre/
|
|
179
|
+
|
|
180
|
+
# pytype static type analyzer
|
|
181
|
+
.pytype/
|
|
182
|
+
|
|
183
|
+
# Cython debug symbols
|
|
184
|
+
cython_debug/
|
|
185
|
+
|
|
186
|
+
# PyCharm
|
|
187
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
188
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
189
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
190
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
191
|
+
# .idea/
|
|
192
|
+
|
|
193
|
+
# Abstra
|
|
194
|
+
# Abstra is an AI-powered process automation framework.
|
|
195
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
196
|
+
# Learn more at https://abstra.io/docs
|
|
197
|
+
.abstra/
|
|
198
|
+
|
|
199
|
+
# Visual Studio Code
|
|
200
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore that
|
|
201
|
+
# can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
202
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer, you
|
|
203
|
+
# could uncomment the following to ignore the entire vscode folder
|
|
204
|
+
# .vscode/
|
|
205
|
+
# Temporary file for partial code execution
|
|
206
|
+
tempCodeRunnerFile.py
|
|
207
|
+
|
|
208
|
+
# Ruff stuff:
|
|
209
|
+
.ruff_cache/
|
|
210
|
+
|
|
211
|
+
# PyPI configuration file
|
|
212
|
+
.pypirc
|
|
213
|
+
|
|
214
|
+
# Marimo
|
|
215
|
+
marimo/_static/
|
|
216
|
+
marimo/_lsp/
|
|
217
|
+
__marimo__/
|
|
218
|
+
|
|
219
|
+
# Streamlit
|
|
220
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.17.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [ --fix ]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
|
|
9
|
+
- repo: local
|
|
10
|
+
hooks:
|
|
11
|
+
- id: check-ops-plain-ascii
|
|
12
|
+
name: check-ops-plain-ascii
|
|
13
|
+
entry: >-
|
|
14
|
+
python3 -c "import sys, pathlib; errors = [f'{p}:{i+1} non-ascii char {c!r}' for p in sys.argv[1:] for i, line in enumerate(pathlib.Path(p).read_text(encoding='utf-8', errors='replace').splitlines()) for c in line if ord(c) > 127]; sys.exit('\n'.join(errors)) if errors else sys.exit(0)"
|
|
15
|
+
language: system
|
|
16
|
+
files: (^|/)ops/.*\.(sh|py|bash|zsh)$|(^|/)ops/deploy$
|
|
17
|
+
|
|
18
|
+
- id: code-limits
|
|
19
|
+
name: check-module-and-function-limits
|
|
20
|
+
entry: /Users/talfranji/venv/afsar/bin/python ops/check_code_limits.py --max-module-lines 600 --max-function-lines 220
|
|
21
|
+
language: system
|
|
22
|
+
types: [python]
|
|
23
|
+
pass_filenames: false
|
|
24
|
+
|
|
25
|
+
- id: xenon
|
|
26
|
+
name: xenon
|
|
27
|
+
entry: /Users/talfranji/venv/afsar/bin/xenon --no-assert -b C -m C -a A src ops -i "tests"
|
|
28
|
+
language: system
|
|
29
|
+
types: [python]
|
|
30
|
+
pass_filenames: false
|
|
31
|
+
|
|
32
|
+
- id: mypy
|
|
33
|
+
name: mypy
|
|
34
|
+
entry: /Users/talfranji/venv/afsar/bin/mypy src ops tests
|
|
35
|
+
language: system
|
|
36
|
+
types: [python]
|
|
37
|
+
pass_filenames: false
|
|
38
|
+
|
|
39
|
+
- id: pytest
|
|
40
|
+
name: pytest
|
|
41
|
+
entry: /Users/talfranji/venv/afsar/bin/pytest -v
|
|
42
|
+
language: system
|
|
43
|
+
types: [python]
|
|
44
|
+
pass_filenames: false
|
afsar-1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: afsar
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: LLM API library for students utilizing local agents or remote APIs
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Provides-Extra: dev
|
|
7
|
+
Requires-Dist: mypy>=1.9.0; extra == 'dev'
|
|
8
|
+
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
|
|
9
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
10
|
+
Requires-Dist: ruff>=0.17.0; extra == 'dev'
|
|
11
|
+
Requires-Dist: xenon>=0.9.0; extra == 'dev'
|
|
12
|
+
Provides-Extra: schema
|
|
13
|
+
Requires-Dist: pydantic>=2.0.0; extra == 'schema'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src="src/afsar/resources/afsar.png" alt="afsar logo" width="180" />
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
# afsar
|
|
21
|
+
|
|
22
|
+
A simple, student-friendly procedural Python library providing a uniform interface to LLMs. `afsar` prioritizes installed local coding agents (`claude-code`, `antigravity`) so students can use their existing agents and tools, and smoothly falls back to remote cloud APIs (`Gemini`, `OpenAI`).
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Install via pip:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install afsar
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quickstart
|
|
37
|
+
|
|
38
|
+
### 1. Basic Text Query
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import afsar
|
|
42
|
+
|
|
43
|
+
# Setup is explicit and required once before asking
|
|
44
|
+
afsar.setup()
|
|
45
|
+
|
|
46
|
+
answer = afsar.ask("How do you define a function in Python?")
|
|
47
|
+
print(answer)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 2. Structured Output with `@dataclass`
|
|
51
|
+
|
|
52
|
+
You don't need Pydantic to get typed objects. Standard Python dataclasses work out of the box:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from dataclasses import dataclass
|
|
56
|
+
import afsar
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class QuizQuestion:
|
|
61
|
+
question: str
|
|
62
|
+
points: int
|
|
63
|
+
topic: str
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
afsar.setup()
|
|
67
|
+
quiz = afsar.ask("Create a beginner Python question.", schema=QuizQuestion)
|
|
68
|
+
|
|
69
|
+
print(quiz.question)
|
|
70
|
+
print(f"Points: {quiz.points}")
|
|
71
|
+
print(f"Topic: {quiz.topic}")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 3. Raw JSON Output
|
|
75
|
+
|
|
76
|
+
If you just want a JSON-formatted string:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
import afsar
|
|
80
|
+
|
|
81
|
+
afsar.setup()
|
|
82
|
+
raw_json = afsar.ask("List 3 Python data structures.", format="json")
|
|
83
|
+
print(raw_json) # Returns clean JSON string
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 4. Student Coding Exercises (Local Tools)
|
|
87
|
+
|
|
88
|
+
When using local agents (`claude` or `agy`), `afsar` defaults to `tools=["python", "files"]`. This allows asking the agent to write and test code locally:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import afsar
|
|
92
|
+
|
|
93
|
+
afsar.setup()
|
|
94
|
+
response = afsar.ask(
|
|
95
|
+
"Write a solution to check if a word is a palindrome, save it to palindrome.py, run it with python, and show the result."
|
|
96
|
+
)
|
|
97
|
+
print(response)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Remote APIs (Gemini & OpenAI)
|
|
103
|
+
|
|
104
|
+
Remote models do not support local file or execution tools. When connecting to remote APIs, explicitly pass `tools=[]`:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
import afsar
|
|
108
|
+
|
|
109
|
+
# Using Gemini
|
|
110
|
+
afsar.setup(provider="gemini", api_key="YOUR_GEMINI_KEY", tools=[])
|
|
111
|
+
|
|
112
|
+
# Or using OpenAI
|
|
113
|
+
afsar.setup(provider="openai", api_key="YOUR_OPENAI_KEY", tools=[])
|
|
114
|
+
|
|
115
|
+
answer = afsar.ask("Explain recursion in simple terms.")
|
|
116
|
+
print(answer)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Auto-Detection & Priority
|
|
122
|
+
|
|
123
|
+
Calling `afsar.setup()` without arguments automatically searches for available providers in this order:
|
|
124
|
+
|
|
125
|
+
1. **Claude Code CLI** (`claude`) in `PATH`
|
|
126
|
+
2. **Antigravity CLI** (`agy`) in `PATH`
|
|
127
|
+
3. **Google Gemini** (`GEMINI_API_KEY` in environment)
|
|
128
|
+
4. **OpenAI** (`OPENAI_API_KEY` in environment)
|
|
129
|
+
|
|
130
|
+
You can override the provider explicitly in code:
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
afsar.setup(provider="agy")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or via the `AFSAR_PROVIDER` environment variable:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
export AFSAR_PROVIDER=agy
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Exceptions
|
|
145
|
+
|
|
146
|
+
- `NotSetupError`: Raised if `afsar.ask()` is called before `afsar.setup()`.
|
|
147
|
+
- `UnsupportedFeatureError`: Raised if requesting local tools (`tools=["python", "files"]`) on remote API providers.
|
|
148
|
+
- `ModelNotFoundError`: Raised if no supported local CLI or remote API key is discovered.
|
|
149
|
+
- `ExecutionError`: Raised if a CLI command or remote API request fails.
|
afsar-1.0/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="src/afsar/resources/afsar.png" alt="afsar logo" width="180" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# afsar
|
|
6
|
+
|
|
7
|
+
A simple, student-friendly procedural Python library providing a uniform interface to LLMs. `afsar` prioritizes installed local coding agents (`claude-code`, `antigravity`) so students can use their existing agents and tools, and smoothly falls back to remote cloud APIs (`Gemini`, `OpenAI`).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Install via pip:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install afsar
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
### 1. Basic Text Query
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import afsar
|
|
27
|
+
|
|
28
|
+
# Setup is explicit and required once before asking
|
|
29
|
+
afsar.setup()
|
|
30
|
+
|
|
31
|
+
answer = afsar.ask("How do you define a function in Python?")
|
|
32
|
+
print(answer)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Structured Output with `@dataclass`
|
|
36
|
+
|
|
37
|
+
You don't need Pydantic to get typed objects. Standard Python dataclasses work out of the box:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
import afsar
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class QuizQuestion:
|
|
46
|
+
question: str
|
|
47
|
+
points: int
|
|
48
|
+
topic: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
afsar.setup()
|
|
52
|
+
quiz = afsar.ask("Create a beginner Python question.", schema=QuizQuestion)
|
|
53
|
+
|
|
54
|
+
print(quiz.question)
|
|
55
|
+
print(f"Points: {quiz.points}")
|
|
56
|
+
print(f"Topic: {quiz.topic}")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3. Raw JSON Output
|
|
60
|
+
|
|
61
|
+
If you just want a JSON-formatted string:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import afsar
|
|
65
|
+
|
|
66
|
+
afsar.setup()
|
|
67
|
+
raw_json = afsar.ask("List 3 Python data structures.", format="json")
|
|
68
|
+
print(raw_json) # Returns clean JSON string
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 4. Student Coding Exercises (Local Tools)
|
|
72
|
+
|
|
73
|
+
When using local agents (`claude` or `agy`), `afsar` defaults to `tools=["python", "files"]`. This allows asking the agent to write and test code locally:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import afsar
|
|
77
|
+
|
|
78
|
+
afsar.setup()
|
|
79
|
+
response = afsar.ask(
|
|
80
|
+
"Write a solution to check if a word is a palindrome, save it to palindrome.py, run it with python, and show the result."
|
|
81
|
+
)
|
|
82
|
+
print(response)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Remote APIs (Gemini & OpenAI)
|
|
88
|
+
|
|
89
|
+
Remote models do not support local file or execution tools. When connecting to remote APIs, explicitly pass `tools=[]`:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import afsar
|
|
93
|
+
|
|
94
|
+
# Using Gemini
|
|
95
|
+
afsar.setup(provider="gemini", api_key="YOUR_GEMINI_KEY", tools=[])
|
|
96
|
+
|
|
97
|
+
# Or using OpenAI
|
|
98
|
+
afsar.setup(provider="openai", api_key="YOUR_OPENAI_KEY", tools=[])
|
|
99
|
+
|
|
100
|
+
answer = afsar.ask("Explain recursion in simple terms.")
|
|
101
|
+
print(answer)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Auto-Detection & Priority
|
|
107
|
+
|
|
108
|
+
Calling `afsar.setup()` without arguments automatically searches for available providers in this order:
|
|
109
|
+
|
|
110
|
+
1. **Claude Code CLI** (`claude`) in `PATH`
|
|
111
|
+
2. **Antigravity CLI** (`agy`) in `PATH`
|
|
112
|
+
3. **Google Gemini** (`GEMINI_API_KEY` in environment)
|
|
113
|
+
4. **OpenAI** (`OPENAI_API_KEY` in environment)
|
|
114
|
+
|
|
115
|
+
You can override the provider explicitly in code:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
afsar.setup(provider="agy")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or via the `AFSAR_PROVIDER` environment variable:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
export AFSAR_PROVIDER=agy
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Exceptions
|
|
130
|
+
|
|
131
|
+
- `NotSetupError`: Raised if `afsar.ask()` is called before `afsar.setup()`.
|
|
132
|
+
- `UnsupportedFeatureError`: Raised if requesting local tools (`tools=["python", "files"]`) on remote API providers.
|
|
133
|
+
- `ModelNotFoundError`: Raised if no supported local CLI or remote API key is discovered.
|
|
134
|
+
- `ExecutionError`: Raised if a CLI command or remote API request fails.
|