prompture 0.0.36__tar.gz → 0.0.37__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.
- {prompture-0.0.36 → prompture-0.0.37}/.env.copy +1 -0
- {prompture-0.0.36 → prompture-0.0.37}/.github/scripts/update_docs_version.py +41 -2
- {prompture-0.0.36 → prompture-0.0.37}/.github/workflows/documentation.yml +7 -4
- {prompture-0.0.36 → prompture-0.0.37}/.github/workflows/publish.yml +1 -1
- {prompture-0.0.36/prompture.egg-info → prompture-0.0.37}/PKG-INFO +1 -1
- prompture-0.0.37/ROADMAP.md +341 -0
- prompture-0.0.37/VERSION +1 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/conf.py +14 -2
- {prompture-0.0.36 → prompture-0.0.37}/prompture/__init__.py +120 -2
- {prompture-0.0.36 → prompture-0.0.37}/prompture/_version.py +2 -2
- prompture-0.0.37/prompture/agent.py +924 -0
- prompture-0.0.37/prompture/agent_types.py +156 -0
- prompture-0.0.37/prompture/async_agent.py +880 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/async_conversation.py +199 -17
- {prompture-0.0.36 → prompture-0.0.37}/prompture/async_driver.py +24 -0
- prompture-0.0.37/prompture/async_groups.py +551 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/conversation.py +213 -18
- {prompture-0.0.36 → prompture-0.0.37}/prompture/core.py +30 -12
- {prompture-0.0.36 → prompture-0.0.37}/prompture/discovery.py +24 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/driver.py +38 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/__init__.py +5 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_azure_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_claude_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_google_driver.py +24 -4
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_grok_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_groq_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_lmstudio_driver.py +59 -3
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_ollama_driver.py +7 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_openai_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_openrouter_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_registry.py +5 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/azure_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/claude_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/google_driver.py +24 -4
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/grok_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/groq_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/lmstudio_driver.py +58 -6
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/ollama_driver.py +7 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/openai_driver.py +7 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/openrouter_driver.py +7 -1
- prompture-0.0.37/prompture/drivers/vision_helpers.py +153 -0
- prompture-0.0.37/prompture/group_types.py +147 -0
- prompture-0.0.37/prompture/groups.py +530 -0
- prompture-0.0.37/prompture/image.py +180 -0
- prompture-0.0.37/prompture/persistence.py +254 -0
- prompture-0.0.37/prompture/persona.py +482 -0
- prompture-0.0.37/prompture/serialization.py +218 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/settings.py +1 -0
- {prompture-0.0.36 → prompture-0.0.37/prompture.egg-info}/PKG-INFO +1 -1
- {prompture-0.0.36 → prompture-0.0.37}/prompture.egg-info/SOURCES.txt +11 -3
- {prompture-0.0.36 → prompture-0.0.37}/pyproject.toml +1 -1
- prompture-0.0.36/.mcp.json +0 -19
- prompture-0.0.36/0.23.0 +0 -8
- prompture-0.0.36/8 +0 -0
- prompture-0.0.36/ROADMAP.md +0 -295
- prompture-0.0.36/VERSION +0 -1
- {prompture-0.0.36 → prompture-0.0.37}/.claude/skills/add-driver/SKILL.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.claude/skills/add-driver/references/driver-template.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.claude/skills/add-example/SKILL.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.claude/skills/add-field/SKILL.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.claude/skills/add-test/SKILL.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.claude/skills/run-tests/SKILL.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.claude/skills/scaffold-extraction/SKILL.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.claude/skills/update-pricing/SKILL.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.github/FUNDING.yml +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.github/scripts/update_wrapper_version.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/.github/workflows/dev.yml +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/CLAUDE.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/LICENSE +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/MANIFEST.in +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/README.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/_static/custom.css +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/api/core.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/api/drivers.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/api/field_definitions.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/api/index.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/api/runner.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/api/tools.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/api/validator.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/contributing.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/examples.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/field_definitions_reference.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/index.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/installation.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/quickstart.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/docs/source/toon_input_guide.rst +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/README.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/llm_to_json/README.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/llm_to_json/llm_to_json/__init__.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/llm_to_json/pyproject.toml +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/llm_to_json/test.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/llm_to_toon/README.md +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/llm_to_toon/llm_to_toon/__init__.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/llm_to_toon/pyproject.toml +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/packages/llm_to_toon/test.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/aio/__init__.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/async_core.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/cache.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/callbacks.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/cli.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/cost_mixin.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/airllm_driver.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_airllm_driver.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_hugging_driver.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/async_local_http_driver.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/hugging_driver.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/local_http_driver.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/drivers/registry.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/field_definitions.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/logging.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/model_rates.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/runner.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/__init__.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/generator.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/templates/Dockerfile.j2 +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/templates/README.md.j2 +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/templates/config.py.j2 +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/templates/env.example.j2 +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/templates/main.py.j2 +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/templates/models.py.j2 +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/scaffold/templates/requirements.txt.j2 +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/server.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/session.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/tools.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/tools_schema.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture/validator.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture.egg-info/dependency_links.txt +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture.egg-info/entry_points.txt +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture.egg-info/requires.txt +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/prompture.egg-info/top_level.txt +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/requirements.txt +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/setup.cfg +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/test.py +0 -0
- {prompture-0.0.36 → prompture-0.0.37}/test_version_diagnosis.py +0 -0
|
@@ -174,6 +174,41 @@ def update_index_rst(index_file, version):
|
|
|
174
174
|
return False
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
def update_conf_py(conf_file, version):
|
|
178
|
+
"""
|
|
179
|
+
Update the release version in docs/source/conf.py fallback.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
conf_file: Path to the conf.py file
|
|
183
|
+
version: Version string to insert
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
bool: True if file was updated, False otherwise
|
|
187
|
+
"""
|
|
188
|
+
if not conf_file.exists():
|
|
189
|
+
print(f"✗ File not found: {conf_file}", file=sys.stderr)
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
content = conf_file.read_text(encoding="utf-8")
|
|
194
|
+
# Update the hardcoded fallback version in conf.py
|
|
195
|
+
new_content, count = re.subn(
|
|
196
|
+
r'(release\s*=\s*")[^"]+(")',
|
|
197
|
+
rf"\g<1>{version}\g<2>",
|
|
198
|
+
content,
|
|
199
|
+
)
|
|
200
|
+
if count > 0:
|
|
201
|
+
conf_file.write_text(new_content, encoding="utf-8")
|
|
202
|
+
print(f"✓ Updated fallback release version in {conf_file}")
|
|
203
|
+
return True
|
|
204
|
+
else:
|
|
205
|
+
print(f"✗ release pattern not found in {conf_file}", file=sys.stderr)
|
|
206
|
+
return False
|
|
207
|
+
except Exception as e:
|
|
208
|
+
print(f"✗ Error updating conf.py: {e}", file=sys.stderr)
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
|
|
177
212
|
def main():
|
|
178
213
|
"""Main entry point for the script."""
|
|
179
214
|
# Determine project root (two levels up from this script)
|
|
@@ -192,9 +227,13 @@ def main():
|
|
|
192
227
|
|
|
193
228
|
# Update index.rst
|
|
194
229
|
index_file = project_root / "docs" / "source" / "index.rst"
|
|
195
|
-
|
|
230
|
+
rst_ok = update_index_rst(index_file, version)
|
|
231
|
+
|
|
232
|
+
# Update conf.py fallback version
|
|
233
|
+
conf_file = project_root / "docs" / "source" / "conf.py"
|
|
234
|
+
conf_ok = update_conf_py(conf_file, version)
|
|
196
235
|
|
|
197
|
-
if
|
|
236
|
+
if rst_ok or conf_ok:
|
|
198
237
|
print("\n✓ Documentation version updated successfully")
|
|
199
238
|
sys.exit(0)
|
|
200
239
|
else:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
name: documentation
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
workflow_run:
|
|
5
|
+
workflows: ["Publish to PyPI and GitHub Release"]
|
|
6
|
+
types: [completed]
|
|
7
7
|
|
|
8
8
|
permissions:
|
|
9
9
|
contents: write
|
|
@@ -11,8 +11,12 @@ permissions:
|
|
|
11
11
|
jobs:
|
|
12
12
|
docs:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
|
+
# Only run if the publish workflow succeeded
|
|
15
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
14
16
|
steps:
|
|
15
17
|
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
ref: main # Ensure we get the latest main (with updated VERSION)
|
|
16
20
|
- uses: actions/setup-python@v5
|
|
17
21
|
- name: Install dependencies
|
|
18
22
|
run: |
|
|
@@ -25,7 +29,6 @@ jobs:
|
|
|
25
29
|
sphinx-build docs/source _build
|
|
26
30
|
- name: Deploy to GitHub Pages
|
|
27
31
|
uses: peaceiris/actions-gh-pages@v3
|
|
28
|
-
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
29
32
|
with:
|
|
30
33
|
publish_branch: gh-pages
|
|
31
34
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -38,7 +38,7 @@ jobs:
|
|
|
38
38
|
echo "${{ steps.bump_version.outputs.new_version }}" > VERSION
|
|
39
39
|
git config user.name "github-actions[bot]"
|
|
40
40
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
41
|
-
git add VERSION
|
|
41
|
+
git add -f VERSION
|
|
42
42
|
git commit -m "🔖 Version v${{ steps.bump_version.outputs.new_version }} [skip ci]" || true
|
|
43
43
|
git push origin main || true
|
|
44
44
|
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# Prompture Roadmap
|
|
2
|
+
|
|
3
|
+
## Completed Work
|
|
4
|
+
|
|
5
|
+
### v0.0.1–v0.0.21: Core Extraction Engine
|
|
6
|
+
- Initial project structure, `ask_for_json()`, JSON schema enforcement
|
|
7
|
+
- OpenAI, Claude, Azure, Ollama drivers
|
|
8
|
+
- JSON cleaning and AI-powered cleanup fallback
|
|
9
|
+
- `extract_and_jsonify()`, `manual_extract_and_jsonify()`
|
|
10
|
+
- Driver flexibility: `get_driver()` interface
|
|
11
|
+
- Example scripts for each provider
|
|
12
|
+
|
|
13
|
+
### v0.0.22–v0.0.24: Pydantic & Stepwise Extraction
|
|
14
|
+
- `extract_with_model()`: one-shot Pydantic model extraction
|
|
15
|
+
- `stepwise_extract_with_model()`: per-field extraction with smart type coercion
|
|
16
|
+
- `tools.py` utilities: parsing, schema generation, shorthand numbers, multilingual booleans
|
|
17
|
+
- Structured logging and verbose control
|
|
18
|
+
|
|
19
|
+
### v0.0.25–v0.0.28: Multi-Provider & Field System
|
|
20
|
+
- LM Studio, Google Gemini, Groq, OpenRouter, Grok drivers (12 total)
|
|
21
|
+
- Sphinx documentation site
|
|
22
|
+
- Field definitions registry with 50+ predefined fields
|
|
23
|
+
- Enum field support and validation utilities
|
|
24
|
+
- Template variables (`{{current_year}}`, `{{current_date}}`, etc.)
|
|
25
|
+
- Text classification and analysis examples
|
|
26
|
+
|
|
27
|
+
### v0.0.29–v0.0.32: TOON, Discovery & AirLLM
|
|
28
|
+
- TOON output format support (compact token-oriented notation)
|
|
29
|
+
- TOON input conversion via `extract_from_data()` / `extract_from_pandas()` (45-60% token savings)
|
|
30
|
+
- `get_available_models()` auto-discovery across configured providers
|
|
31
|
+
- `render_output()` for raw text/HTML/markdown generation
|
|
32
|
+
- AirLLM driver for local inference
|
|
33
|
+
- Live model rates with caching and `get_model_rates()` API
|
|
34
|
+
|
|
35
|
+
### v0.0.33–v0.0.34: Async, Caching & Conversations
|
|
36
|
+
- `AsyncDriver` base class and async driver implementations
|
|
37
|
+
- `AsyncConversation` for non-blocking multi-turn interactions
|
|
38
|
+
- Response caching with memory, SQLite, and Redis backends
|
|
39
|
+
- `Conversation` class: stateful multi-turn sessions with system prompts and message history
|
|
40
|
+
- Message-based driver APIs: `generate_messages()`, `generate_messages_stream()`
|
|
41
|
+
- Native JSON mode detection per provider (OpenAI `json_schema`, Claude tool-use, Gemini `response_mime_type`)
|
|
42
|
+
- `DriverCallbacks` with `on_request`, `on_response`, `on_error`, `on_stream_delta` hooks
|
|
43
|
+
- `UsageSession` for accumulated token/cost tracking across calls
|
|
44
|
+
- `configure_logging()` with `JSONFormatter` for structured log output
|
|
45
|
+
|
|
46
|
+
### v0.0.35: Tool Use, Streaming & Plugin System
|
|
47
|
+
- `ToolRegistry` and `ToolDefinition`: register Python functions as LLM-callable tools
|
|
48
|
+
- `tool_from_function()`: auto-generate JSON schemas from type hints
|
|
49
|
+
- Tool use in conversations with multi-round execution (`max_tool_rounds`)
|
|
50
|
+
- Streaming via `ask_stream()` and `generate_messages_stream()`
|
|
51
|
+
- Pluggable driver registry with entry-point discovery
|
|
52
|
+
- `register_driver()` / `register_async_driver()` for third-party provider plugins
|
|
53
|
+
|
|
54
|
+
### v0.0.36 (current): Vision Support
|
|
55
|
+
- `ImageContent` frozen dataclass and `make_image()` smart constructor for bytes, base64, file path, URL inputs
|
|
56
|
+
- `image_from_bytes()`, `image_from_base64()`, `image_from_file()`, `image_from_url()` constructors
|
|
57
|
+
- `conv.ask("describe", images=[screenshot_bytes])` API on `Conversation` and `AsyncConversation`
|
|
58
|
+
- Image support in `ask_for_json()`, `extract_with_model()`, `ask_stream()`, `add_context()`
|
|
59
|
+
- Image support in standalone core functions: `render_output()`, `ask_for_json()`, `extract_and_jsonify()`, `extract_with_model()`
|
|
60
|
+
- Driver-level `_prepare_messages()` with provider-specific wire formats (OpenAI, Claude, Gemini, Ollama)
|
|
61
|
+
- Shared `vision_helpers.py` module for OpenAI-compatible drivers (Groq, Grok, Azure, LM Studio, OpenRouter)
|
|
62
|
+
- `supports_vision` capability flag on all drivers (sync and async)
|
|
63
|
+
- Universal internal format: `{"type": "image", "source": ImageContent(...)}` content blocks
|
|
64
|
+
- Backward compatible: string-only messages unchanged
|
|
65
|
+
|
|
66
|
+
### v0.0.37: Conversation Persistence
|
|
67
|
+
- `conv.export() -> dict` serialization (messages, system prompt, tool definitions, usage)
|
|
68
|
+
- `Conversation.from_export(data)` restoration with driver reconstruction
|
|
69
|
+
- File-based persistence: `conv.save("path.json")` / `Conversation.load("path.json")`
|
|
70
|
+
- SQLite `ConversationStore` backend with tag search, listing, and CRUD
|
|
71
|
+
- Optional auto-save on every turn via `auto_save` parameter
|
|
72
|
+
- Conversation metadata: `conversation_id`, `tags`, `created_at`, `last_active`, `turn_count`
|
|
73
|
+
- Export/import of `UsageSession` alongside conversation state
|
|
74
|
+
- `ImageContent` serialization/deserialization with `strip_images` option
|
|
75
|
+
- Versioned export format (`EXPORT_VERSION = 1`) with validation on import
|
|
76
|
+
- `serialization.py` (pure data transforms) and `persistence.py` (storage backends) modules
|
|
77
|
+
- Full async support: mirrored on `AsyncConversation`
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Upcoming
|
|
82
|
+
|
|
83
|
+
### Phase 3: Agent Framework
|
|
84
|
+
**Goal**: Higher-level agent abstraction with a ReAct loop, typed context, structured output, and two-tier execution API (simple `run()` + step-by-step `iter()`).
|
|
85
|
+
|
|
86
|
+
#### Phase 3a: Core Agent Class ✅
|
|
87
|
+
- [x] `Agent` class composing `Conversation` + `ToolRegistry` + system prompt + output type
|
|
88
|
+
- [x] Constructor: `Agent(model, *, driver, tools, system_prompt, output_type, max_iterations, options)`
|
|
89
|
+
- [x] Tool registration via constructor injection (list or `ToolRegistry`) and `@agent.tool` decorator
|
|
90
|
+
- [x] `output_type: type[BaseModel]` for structured agent output with JSON parse + `model_validate()` retry (up to 3 attempts)
|
|
91
|
+
- [x] `agent.run(prompt) -> AgentResult` — high-level, hides the ReAct loop entirely
|
|
92
|
+
- [x] `AgentResult` containing: `output` (typed or str), `output_text`, `messages`, `usage`, `steps: list[AgentStep]`, `all_tool_calls`, `state`
|
|
93
|
+
- [x] `AgentStep` dataclass with `step_type` (think/tool_call/tool_result/output), `timestamp`, `content`, `tool_name`, `tool_args`, `tool_result`, `duration_ms`
|
|
94
|
+
- [x] `AgentState` enum: `idle`, `running`, `stopped`, `errored`
|
|
95
|
+
- [x] `agent.stop()` graceful shutdown flag
|
|
96
|
+
- [x] Fresh `Conversation` per `run()` — no state leakage between runs
|
|
97
|
+
- [x] Internal ReAct loop delegates to `Conversation._ask_with_tools` via `max_tool_rounds`
|
|
98
|
+
- [x] System prompt augmented with JSON schema instructions when `output_type` is set
|
|
99
|
+
- [x] `ModelRetry` exception defined (used in Phase 3b guardrails)
|
|
100
|
+
- [x] Shared types module: `agent_types.py` (AgentState, StepType, AgentStep, AgentResult, ModelRetry)
|
|
101
|
+
- [x] 24 unit tests covering construction, run (no tools / with tools / with output_type), system prompt, stop, state, options
|
|
102
|
+
- [x] Example script: `examples/agent_example.py`
|
|
103
|
+
|
|
104
|
+
#### Phase 3b: Context, Guardrails & Callbacks ✅
|
|
105
|
+
- [x] `RunContext[DepsType]` dataclass passed to tools and system prompt functions: carries deps, model info, usage, message history, iteration count
|
|
106
|
+
- [x] `deps_type` generic on `Agent` for type-safe dependency access in tools
|
|
107
|
+
- [x] Dynamic system prompts: `system_prompt: str | Callable[[RunContext], str]` for context-aware persona rendering
|
|
108
|
+
- [x] `AgentCallbacks` extending `DriverCallbacks` with: `on_step`, `on_tool_start(name, args)`, `on_tool_end(name, result)`, `on_iteration(step_number)`, `on_output(result)`
|
|
109
|
+
- [x] Input validators: `input_guardrails: list[Callable[[RunContext, str], str | None]]` — transform or reject input before loop starts
|
|
110
|
+
- [x] Output validators: `output_guardrails: list[Callable[[RunContext, AgentResult], AgentResult | None]]` — validate final output, raise `ModelRetry` to feed error back to LLM
|
|
111
|
+
- [x] `ModelRetry` exception integration: raised from tools or validators to send error message back to the model with retry budget
|
|
112
|
+
- [x] Per-run `UsageSession` tracking (tokens, cost, errors across all iterations)
|
|
113
|
+
- [x] Iteration limits: `max_cost` (USD budget via `UsageSession`)
|
|
114
|
+
|
|
115
|
+
#### Phase 3c: Streaming, Iteration & Async ✅
|
|
116
|
+
- [x] `agent.iter(prompt) -> AgentIterator` — low-level step-by-step control, yields `AgentStep` per iteration
|
|
117
|
+
- [x] `agent.run_stream(prompt) -> StreamedAgentResult` — streaming with deltas for each step
|
|
118
|
+
- [x] `AsyncAgent` mirroring `Agent` with `async run()`, `async iter()`, `async run_stream()`
|
|
119
|
+
- [x] Async tool support: tools can be sync or async callables (auto-detected)
|
|
120
|
+
- [x] `StreamEvent` and `StreamEventType` types for streaming event classification
|
|
121
|
+
- [x] `AsyncAgentIterator` and `AsyncStreamedAgentResult` wrapper classes with result capture
|
|
122
|
+
- [x] All new types exported from `prompture.__init__`
|
|
123
|
+
- [x] Example script: `examples/async_agent_example.py`
|
|
124
|
+
|
|
125
|
+
### Phase 4: Persona Templates ✅
|
|
126
|
+
**Goal**: Reusable, composable system prompt definitions with template variables, layered composition, and a thread-safe registry following the `field_definitions.py` pattern.
|
|
127
|
+
|
|
128
|
+
#### Persona Data Model ✅
|
|
129
|
+
- [x] `Persona` frozen dataclass with structured fields: `name`, `system_prompt` (template text), `description` (metadata for registries/docs), `traits: tuple[str, ...]` (behavioral tags), `variables: dict[str, Any]` (default template values), `constraints: list[str]` (rules and guardrails), `model_hint: str | None` (suggested model), `settings: dict[str, Any]` (temperature, max_tokens, etc.)
|
|
130
|
+
- [x] Layered prompt structure within `system_prompt`: role/identity section, behavioral rules, output format constraints, and domain knowledge — constraints rendered as `## Constraints` section
|
|
131
|
+
|
|
132
|
+
#### Template Rendering ✅
|
|
133
|
+
- [x] Reuse `field_definitions.py` `_apply_templates()` and `_get_template_variables()` for persona prompts
|
|
134
|
+
- [x] Built-in runtime variables: `{{current_date}}`, `{{current_year}}`, `{{current_datetime}}`, `{{current_weekday}}` (same as field definitions)
|
|
135
|
+
- [x] Custom per-render variables: `persona.render(user_name="Alice", company="Acme")`
|
|
136
|
+
- [x] `Persona.render(**kwargs) -> str` produces final system prompt with all variables resolved
|
|
137
|
+
- [x] Variable precedence: built-in < `self.variables` < kwargs
|
|
138
|
+
|
|
139
|
+
#### Composition & Extension ✅
|
|
140
|
+
- [x] `persona.extend(additional_instructions) -> Persona` — returns new persona with appended instructions (immutable via `dataclasses.replace`)
|
|
141
|
+
- [x] Trait composition: `Persona(traits=("concise", "technical"))` resolves traits from registry during `render()`
|
|
142
|
+
- [x] Trait registry: `register_trait("concise", "Keep responses under 3 sentences.")`, `get_trait()`, `get_trait_names()`, `reset_trait_registry()` with `threading.Lock`
|
|
143
|
+
- [x] Constraint injection: `persona.with_constraints(["Never discuss competitors"])` — appends rules without modifying the base prompt
|
|
144
|
+
- [x] `persona + other_persona` merge operator: concatenates prompts, dedupes traits, merges variables (right wins + warning on conflict), merges constraints and settings
|
|
145
|
+
|
|
146
|
+
#### Thread-Safe Global Registry ✅
|
|
147
|
+
- [x] `register_persona(persona)` / `get_persona(name)` with `threading.Lock` (mirrors `field_definitions.py` pattern)
|
|
148
|
+
- [x] `get_persona_names()`, `get_persona_registry_snapshot()`, `clear_persona_registry()`, `reset_persona_registry()`
|
|
149
|
+
- [x] `_PersonaRegistryProxy` for dict-like access: `PERSONAS["analyst"]`
|
|
150
|
+
- [x] Auto-initialization with built-in personas on import
|
|
151
|
+
|
|
152
|
+
#### Built-in Personas ✅
|
|
153
|
+
- [x] `json_extractor` — precise structured data extraction, `temperature: 0.0`, JSON-only output constraints
|
|
154
|
+
- [x] `data_analyst` — quantitative analysis, cites sources, confidence level constraints
|
|
155
|
+
- [x] `text_summarizer` — concise summaries, configurable `{{max_sentences}}` variable (default 3)
|
|
156
|
+
- [x] `code_reviewer` — structured feedback format (Summary/Issues/Suggestions sections)
|
|
157
|
+
- [x] `concise_assistant` — brief responses, no unnecessary elaboration
|
|
158
|
+
|
|
159
|
+
#### Integration with Conversation & Agent ✅
|
|
160
|
+
- [x] `Conversation(persona="json_extractor")` shorthand — looks up registry, renders, sets as system prompt
|
|
161
|
+
- [x] `Conversation(persona=my_persona)` — accepts `Persona` instance directly
|
|
162
|
+
- [x] `ValueError` when both `persona` and `system_prompt` provided
|
|
163
|
+
- [x] `persona.settings` applied as default options (explicit options override)
|
|
164
|
+
- [x] `persona.model_hint` used if `model_name` not provided
|
|
165
|
+
- [x] Dynamic persona support for agents: `system_prompt: str | Persona | Callable[[RunContext], str]` — Persona objects auto-render with RunContext variables
|
|
166
|
+
- [x] `description` field available for multi-agent routing
|
|
167
|
+
- [x] Full async support: mirrored on `AsyncConversation` and `AsyncAgent`
|
|
168
|
+
- [x] All new symbols exported from `prompture.__init__`
|
|
169
|
+
|
|
170
|
+
#### Serialization & Persistence ✅
|
|
171
|
+
- [x] `persona.to_dict() -> dict` / `Persona.from_dict(data) -> Persona` with `version: 1`
|
|
172
|
+
- [x] JSON file support: `persona.save_json()` / `Persona.load_json()`
|
|
173
|
+
- [x] YAML file support: `persona.save_yaml()` / `Persona.load_yaml()` (optional `pyyaml` dependency)
|
|
174
|
+
- [x] `load_personas_from_directory("personas/")` — bulk-load `.json`/`.yaml`/`.yml` files into the registry
|
|
175
|
+
- [x] 72 unit tests covering all sub-phases
|
|
176
|
+
|
|
177
|
+
### Phase 5: Multi-Agent Coordination ✅
|
|
178
|
+
**Goal**: Enable multiple agents to collaborate via deterministic workflow groups (sequential, parallel, router) and agent-as-tool composition, with explicit scoped state sharing and aggregate usage tracking.
|
|
179
|
+
|
|
180
|
+
#### Agent-as-Tool (Foundation Pattern) ✅
|
|
181
|
+
- [x] `agent.as_tool(name, description) -> ToolDefinition` — wraps any `Agent` as a callable tool for another agent
|
|
182
|
+
- [x] Coordinator retains conversation control; sub-agent runs independently and returns result as tool output
|
|
183
|
+
- [x] Optional `custom_output_extractor: Callable[[AgentResult], str]` for transforming sub-agent results before returning to coordinator
|
|
184
|
+
- [x] Sub-agent inherits no conversation history from coordinator (maximum isolation)
|
|
185
|
+
- [x] Sub-agent `AgentResult` captured for tracing even when used as tool
|
|
186
|
+
|
|
187
|
+
#### Deterministic Workflow Groups ✅
|
|
188
|
+
- [x] `SequentialGroup(agents, *, state, error_policy, max_total_turns)` — agents execute in order, each receiving shared state with outputs from prior agents
|
|
189
|
+
- [x] `ParallelGroup(agents, *, state, error_policy, timeout_ms)` — independent agents run concurrently via `asyncio.gather`, results collected into shared state
|
|
190
|
+
- [x] `LoopGroup(agents, *, exit_condition, max_iterations)` — generator-critic cycle: agents execute in sequence repeatedly until `exit_condition(state) -> bool` returns True
|
|
191
|
+
- [x] All groups accept `state: dict[str, Any]` as initial shared context
|
|
192
|
+
- [x] Groups are composable: a `SequentialGroup` can contain a `ParallelGroup` as a step (nested workflows via `GroupAsAgent` adapter)
|
|
193
|
+
|
|
194
|
+
#### Shared State via Named Keys ✅
|
|
195
|
+
- [x] Each agent reads from shared `state: dict[str, Any]` (injected via template variable substitution in prompts)
|
|
196
|
+
- [x] Each agent writes output to a named key: `Agent(output_key="research_data")` — result stored in `state["research_data"]`
|
|
197
|
+
- [x] Template variable injection: agent system prompts can reference `{research_data}` to read other agents' outputs (Google ADK pattern)
|
|
198
|
+
- [x] Explicit data flow: traceable which agent produces and consumes which state keys
|
|
199
|
+
- [x] No shared conversation history — each agent gets only the state keys it needs (minimum necessary context)
|
|
200
|
+
|
|
201
|
+
#### LLM-Driven Router ✅
|
|
202
|
+
- [x] `RouterAgent(model, agents, routing_prompt)` — uses a (cheap) LLM to classify input and delegate to the appropriate specialist agent
|
|
203
|
+
- [x] Routing based on agent `description` fields from Persona metadata (Phase 4 integration)
|
|
204
|
+
- [x] Fallback agent when no specialist matches
|
|
205
|
+
- [x] Router runs a single LLM call for classification, not a full ReAct loop (minimal overhead)
|
|
206
|
+
|
|
207
|
+
#### Error Handling ✅
|
|
208
|
+
- [x] `ErrorPolicy` enum: `fail_fast` (abort group on first failure), `continue_on_error` (skip failed agent, proceed with partial results), `retry_failed` (retry N times with backoff)
|
|
209
|
+
- [x] Per-agent error state captured in `GroupResult.agent_results` and `GroupResult.errors` (list of `AgentError`)
|
|
210
|
+
- [x] Failed agent's error message available in shared state for downstream agents to handle
|
|
211
|
+
- [x] `max_total_turns` across entire group to prevent runaway costs from agents bouncing between each other
|
|
212
|
+
|
|
213
|
+
#### Group-Level Usage & Observability ✅
|
|
214
|
+
- [x] `GroupResult` dataclass: `agent_results: dict[str, AgentResult]`, `aggregate_usage: dict`, `shared_state: dict[str, Any]`, `elapsed_ms: float`, `timeline: list[GroupStep]`
|
|
215
|
+
- [x] `GroupStep` dataclass: `agent_name`, `step_type` (agent_run/agent_error), `timestamp`, `duration_ms`, `usage_delta`
|
|
216
|
+
- [x] Aggregate usage across all agents via `_aggregate_usage()` helper (prompt_tokens, completion_tokens, total_tokens, total_cost, call_count, errors)
|
|
217
|
+
- [x] `GroupCallbacks` with: `on_agent_start(name, prompt)`, `on_agent_complete(name, result)`, `on_agent_error(name, error)`, `on_state_update(key, value)`
|
|
218
|
+
- [x] Interleaved timeline view: all agent steps merged chronologically for debugging
|
|
219
|
+
|
|
220
|
+
#### Timeout & Cancellation ✅
|
|
221
|
+
- [x] Per-agent timeout in `ParallelGroup(timeout_ms=30000)` — enforced via `asyncio.wait_for`
|
|
222
|
+
- [x] Cooperative shutdown: `group.stop()` calls `agent.stop()` on all running agents
|
|
223
|
+
- [x] `max_total_cost` budget across the group (aggregate `UsageSession` enforced)
|
|
224
|
+
|
|
225
|
+
#### Async Support ✅
|
|
226
|
+
- [x] `AsyncSequentialGroup`, `AsyncLoopGroup` mirroring sync variants
|
|
227
|
+
- [x] `ParallelGroup` uses `asyncio.gather` internally (async-native); sync wrapper available via `group.run()` with event loop management
|
|
228
|
+
- [x] `AsyncRouterAgent` for non-blocking routing
|
|
229
|
+
|
|
230
|
+
#### Serialization & Persistence ✅
|
|
231
|
+
- [x] `GroupResult.export() -> dict` with per-agent results, shared state, aggregate usage, and timeline
|
|
232
|
+
- [x] `GroupResult.save("path.json")` for full group result persistence (reuses `serialization.py` patterns)
|
|
233
|
+
|
|
234
|
+
### Phase 6: Cost Budgets & Guardrails
|
|
235
|
+
**Goal**: Prevent runaway costs with pre-flight estimation and enforcement, manage context windows with token-aware history truncation/summarization, rate-limit requests, and validate input/output content — building on the existing `UsageSession`, `DriverCallbacks`, and `CostMixin` infrastructure.
|
|
236
|
+
|
|
237
|
+
#### Pre-Flight Cost Estimation
|
|
238
|
+
- [ ] `estimate_tokens(text) -> int` using tiktoken (OpenAI models) with fallback to character-based heuristic (~4 chars/token) for other providers
|
|
239
|
+
- [ ] `estimate_cost(prompt, model, options) -> float` — pre-call cost estimate using `get_model_rates()` pricing data
|
|
240
|
+
- [ ] Token count available in `on_request` callback payload: `{"estimated_tokens": int, "estimated_cost": float}` for pre-call decision making
|
|
241
|
+
- [ ] Optional tiktoken dependency: graceful fallback to heuristic when not installed
|
|
242
|
+
|
|
243
|
+
#### Budget Limits & Enforcement
|
|
244
|
+
- [ ] `Conversation(max_cost=0.50, max_tokens=10000)` — per-conversation budget caps
|
|
245
|
+
- [ ] `Agent(max_cost=1.00, max_tokens=50000)` — per-agent-run budget caps (checked between iterations via `UsageSession`)
|
|
246
|
+
- [ ] `BudgetPolicy` enum: `hard_stop` (raise `BudgetExceeded` before the call that would exceed), `warn_and_continue` (fire `on_budget_warning` callback, proceed), `degrade` (switch to cheaper fallback model)
|
|
247
|
+
- [ ] `BudgetExceeded` exception with `usage_at_limit: dict` containing tokens/cost consumed when the limit was hit
|
|
248
|
+
- [ ] Pre-call budget check: compare `estimate_cost()` against remaining budget before each LLM call — reject if estimated cost would exceed remaining budget
|
|
249
|
+
- [ ] Post-call budget check: after each response, update `UsageSession` and check against limits for the *next* call
|
|
250
|
+
- [ ] `on_budget_warning(usage, limit, remaining)` callback fired when usage exceeds configurable threshold (default 80%)
|
|
251
|
+
|
|
252
|
+
#### Model Fallback Chains
|
|
253
|
+
- [ ] `Conversation(model="openai/gpt-4o", fallback_models=["openai/gpt-4o-mini", "groq/llama-3.1-8b"])` — ordered list of progressively cheaper models
|
|
254
|
+
- [ ] Fallback triggers: `BudgetPolicy.degrade` switches to next model in chain when budget threshold reached
|
|
255
|
+
- [ ] Fallback on error: retry with next model on provider errors (rate limit, timeout, 5xx) — configurable via `fallback_on_errors: bool`
|
|
256
|
+
- [ ] `on_model_fallback(from_model, to_model, reason)` callback for observability
|
|
257
|
+
- [ ] Fallback state tracked in `UsageSession`: which models were used and why
|
|
258
|
+
|
|
259
|
+
#### Per-Session & Per-Conversation Tracking
|
|
260
|
+
- [ ] Automatic `UsageSession` on every `Conversation` and `Agent` (no manual callback wiring required)
|
|
261
|
+
- [ ] `conversation.usage_session` property exposing the session with per-model bucketing
|
|
262
|
+
- [ ] `conversation.remaining_budget -> dict` with `{"cost": float, "tokens": int}` remaining before limits
|
|
263
|
+
- [ ] Cross-conversation session: `UsageSession` can be shared across multiple conversations via constructor injection for global budget enforcement
|
|
264
|
+
|
|
265
|
+
#### Rate Limiting
|
|
266
|
+
- [ ] `RateLimiter` class with token bucket algorithm: `RateLimiter(requests_per_minute=60, tokens_per_minute=100000)`
|
|
267
|
+
- [ ] Per-conversation rate limiting: `Conversation(rate_limiter=my_limiter)`
|
|
268
|
+
- [ ] Per-model rate limiting: `RateLimiter` scoped to a specific `"provider/model"` string
|
|
269
|
+
- [ ] Backpressure behavior: `block` (sleep until bucket refills), `reject` (raise `RateLimitExceeded` immediately)
|
|
270
|
+
- [ ] Rate limiter state exposed: `limiter.available_requests`, `limiter.available_tokens`, `limiter.next_available_at`
|
|
271
|
+
|
|
272
|
+
#### Context Window Management
|
|
273
|
+
- [ ] `ContextWindowManager` for token-aware message history management
|
|
274
|
+
- [ ] Token budget allocation: configurable split between system prompt, conversation history, and response — `ContextWindowManager(system_reserve=500, response_reserve=1000, max_context=128000)`
|
|
275
|
+
- [ ] Context window sizes loaded from `get_model_info()` per-model metadata (falls back to configurable default)
|
|
276
|
+
- [ ] Overflow strategy enum: `truncate_oldest` (drop oldest messages first), `summarize` (LLM-compress old messages), `sliding_window` (keep last N messages)
|
|
277
|
+
- [ ] `truncate_oldest`: removes oldest non-system messages until history fits within budget, preserving system prompt and most recent messages
|
|
278
|
+
- [ ] `sliding_window`: keeps last N turns (configurable `window_size`), drops everything before
|
|
279
|
+
- [ ] `Conversation(context_manager=my_manager)` integration — automatically applied before each LLM call
|
|
280
|
+
|
|
281
|
+
#### Conversation Summarization
|
|
282
|
+
- [ ] `summarize` overflow strategy: when history exceeds token budget, compress older messages into a summary using a (cheap) LLM call
|
|
283
|
+
- [ ] Summary inserted as a system-level context message: `{"role": "system", "content": "Previous conversation summary: ..."}`
|
|
284
|
+
- [ ] Configurable summarization model: `ContextWindowManager(summarize_model="openai/gpt-4o-mini")` — use a cheap/fast model for summarization
|
|
285
|
+
- [ ] Hybrid approach: keep last N messages verbatim + summary of everything before (LangChain `ConversationSummaryBufferMemory` pattern)
|
|
286
|
+
- [ ] Summary token budget: summary itself has a max token allocation to prevent unbounded growth
|
|
287
|
+
- [ ] `on_summarize(original_tokens, summary_tokens, messages_removed)` callback for observability
|
|
288
|
+
|
|
289
|
+
#### Content Guardrails
|
|
290
|
+
- [ ] `InputGuardrail` protocol: `check(content: str) -> GuardrailResult` returning `passed`, `blocked` (with reason), or `modified` (with transformed content)
|
|
291
|
+
- [ ] `OutputGuardrail` protocol: `check(content: str, context: dict) -> GuardrailResult` — same return types, with access to conversation context
|
|
292
|
+
- [ ] Built-in input guardrails: `RegexBlocker(patterns: list[str])` for blocking patterns (PII, secrets, profanity), `RegexRequirer(patterns: list[str])` for requiring patterns in output
|
|
293
|
+
- [ ] Built-in output guardrails: `JsonSchemaValidator(schema)` for format compliance, `MaxLengthValidator(max_chars)` for response length
|
|
294
|
+
- [ ] `Conversation(input_guardrails=[...], output_guardrails=[...])` — applied automatically before/after each LLM call
|
|
295
|
+
- [ ] `GuardrailResult.blocked` raises `ContentBlocked(reason, content)` exception
|
|
296
|
+
- [ ] `GuardrailResult.modified` transparently transforms content and proceeds
|
|
297
|
+
- [ ] Guardrail chain: multiple guardrails execute in order; first `blocked` result stops the chain
|
|
298
|
+
- [ ] `on_guardrail_triggered(guardrail_name, result, direction)` callback for logging/observability
|
|
299
|
+
|
|
300
|
+
#### Integration with Agent & Multi-Agent (Phase 3 & 5)
|
|
301
|
+
- [ ] Agent inherits conversation-level budgets and guardrails
|
|
302
|
+
- [ ] `Agent(input_guardrails, output_guardrails)` — Phase 3b guardrails implemented using this Phase 6 infrastructure
|
|
303
|
+
- [ ] Group-level budgets from Phase 5 (`max_total_cost`) enforced via shared `UsageSession` with Phase 6 `BudgetPolicy`
|
|
304
|
+
- [ ] `ModelRetry` (from Phase 3b) integrates with output guardrails: guardrail returns `retry` result → feeds error back to LLM
|
|
305
|
+
|
|
306
|
+
#### Settings & Configuration
|
|
307
|
+
- [ ] `Settings` additions: `default_max_cost`, `default_max_tokens`, `default_rate_limit_rpm`, `default_context_overflow_strategy`
|
|
308
|
+
- [ ] Environment variable support: `PROMPTURE_MAX_COST=0.50`, `PROMPTURE_MAX_TOKENS=10000`, `PROMPTURE_RATE_LIMIT_RPM=60`
|
|
309
|
+
- [ ] All budget/guardrail settings overridable per-conversation or per-agent (constructor params take precedence over Settings defaults)
|
|
310
|
+
|
|
311
|
+
### Phase 7: Async Tool Execution
|
|
312
|
+
**Goal**: Non-blocking tool execution for long-running operations.
|
|
313
|
+
|
|
314
|
+
- [ ] `@registry.async_tool` decorator for async tool functions
|
|
315
|
+
- [ ] Tool timeout configuration per tool
|
|
316
|
+
- [ ] Parallel tool execution when LLM requests multiple tools in one turn
|
|
317
|
+
- [ ] Tool status polling: tool returns "pending" and agent checks back
|
|
318
|
+
- [ ] Tool cancellation support
|
|
319
|
+
- [ ] Progress reporting from tools back to the conversation
|
|
320
|
+
|
|
321
|
+
### Phase 8: Middleware & Interceptors
|
|
322
|
+
**Goal**: Pluggable pipeline between conversation and driver for cross-cutting concerns.
|
|
323
|
+
|
|
324
|
+
- [ ] `Middleware` protocol: `process(message, next) -> message`
|
|
325
|
+
- [ ] Built-in middleware: content filtering, prompt compression, rate limiting
|
|
326
|
+
- [ ] History summarization middleware: compress old messages to save tokens
|
|
327
|
+
- [ ] Logging middleware: structured request/response logging
|
|
328
|
+
- [ ] Retry middleware: automatic retry with backoff on transient errors
|
|
329
|
+
- [ ] `Conversation(middleware=[filter, compress, log])` configuration
|
|
330
|
+
- [ ] Middleware ordering and priority
|
|
331
|
+
|
|
332
|
+
### Phase 9: Structured Observation Input
|
|
333
|
+
**Goal**: Typed input models for feeding structured context to conversations and agents.
|
|
334
|
+
|
|
335
|
+
- [ ] `Observation` base model for structured input (screen state, metrics, events)
|
|
336
|
+
- [ ] Observation-to-prompt template rendering
|
|
337
|
+
- [ ] Built-in observation types: `ScreenObservation`, `MetricsObservation`, `EventObservation`
|
|
338
|
+
- [ ] Custom observation models via Pydantic
|
|
339
|
+
- [ ] `conv.observe(ScreenObservation(app="Chrome", elements=[...]))` API
|
|
340
|
+
- [ ] Automatic observation diffing: only send what changed since last observation
|
|
341
|
+
- [ ] Observation history alongside message history
|
prompture-0.0.37/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.37
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
|
+
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
# Add the project root to the Python path so Sphinx can find the prompture package
|
|
10
11
|
sys.path.insert(0, os.path.abspath("../../"))
|
|
@@ -13,9 +14,20 @@ sys.path.insert(0, os.path.abspath("../../"))
|
|
|
13
14
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
|
14
15
|
|
|
15
16
|
project = "Prompture"
|
|
16
|
-
copyright =
|
|
17
|
+
copyright = '2026, <a href="https://juandenis.com">Juan Denis</a>'
|
|
17
18
|
author = "Juan Denis"
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
# Read version dynamically: VERSION file > setuptools_scm > fallback
|
|
21
|
+
_project_root = Path(__file__).resolve().parent.parent.parent
|
|
22
|
+
_version_file = _project_root / "VERSION"
|
|
23
|
+
if _version_file.exists():
|
|
24
|
+
release = _version_file.read_text().strip()
|
|
25
|
+
else:
|
|
26
|
+
try:
|
|
27
|
+
from setuptools_scm import get_version
|
|
28
|
+
release = get_version(root=str(_project_root))
|
|
29
|
+
except Exception:
|
|
30
|
+
release = "0.0.36"
|
|
19
31
|
|
|
20
32
|
# -- General configuration ---------------------------------------------------
|
|
21
33
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|