Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11", "3.12"]
python-version: ["3.11", "3.14"]

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -80,8 +80,8 @@ jobs:
- name: Install dependencies
run: uv sync --all-extras

- name: Run mypy
run: uv run mypy src
- name: Run ty
run: uv run ty check

docs:
name: Build Documentation
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.11', '3.12']
python-version: ['3.11', '3.14']

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -61,5 +61,5 @@ jobs:
- name: Run ruff
run: uv run ruff check src tests

- name: Run mypy
run: uv run mypy src
- name: Run ty
run: uv run ty check
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ ENV/
htmlcov/
.tox/

# Type checking
.mypy_cache/
.dmypy.json
dmypy.json

# Ruff
.ruff_cache/

Expand Down
111 changes: 98 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,119 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.8] - 2025-10-31

### Changed
- Lazy import of `openai` to reduce memory footprint
- Simplified callback inputs and added telemetry support

## [0.1.7] - 2025-10-31

### Added
- History class for conversation management
- Optional tool execution with `auto_execute_tools` parameter
- Comprehensive documentation and examples
- ReAct now passes kwargs through to underlying Predict modules

### Changed
- Wrapped OpenAI API calls in `_acomplete` for tracing support

## [0.1.6] - 2025-10-31

### Added
- Comprehensive provider configuration guide for LM
- Improved environment variable precedence for multi-provider support

### Changed
- Renamed example files to remove redundant "_example" suffix
- Simplified LM factory implementation
- Updated env var name to `UDSPY_LM_OPENAI_COMPATIBLE_BASE_URL`
- Removed unused history parameter from ReactContext

### Fixed
- Type checking error in settings.py for default_model property
- API key precedence documentation to match implementation

## [0.1.0] - Initial Release
## [0.1.5] - 2025-10-31

### Added
- Auto-initialize LM from `UDSPY_LM_MODEL` environment variable
- History tracking integration in ReAct module
- Comprehensive version validation for releases

## [0.1.4] - 2025-10-31

### Changed
- Use `importlib.metadata` for version instead of hardcoded string

### Fixed
- Type annotations to resolve mypy errors
- Adapter `remaining_content` initialization before conditional assignment

## [0.1.3] - 2025-10-31

### Added
- LM abstraction layer with registry-based factory and multi-provider support (OpenAI, Groq, Bedrock, Ollama)
- LM callable interface: `lm("prompt")` returns text directly, `lm(messages)` returns full response
- DSPy-compatible callback system for telemetry and monitoring
- Confirmation system (renamed from interrupt system) with `ConfirmationRequired`, `ConfirmationRejected`, `ResumeState`
- `set_system_message()` for guaranteed system prompt positioning in History
- `module` and `is_final` attributes on Prediction for nested module support
- String signature support: `Predict("question -> answer")`
- jiter-based JSON streaming parser for adapter
- `parse_and_validate_args` for Pydantic model arguments in tools
- `run_async_with_context` for context preservation in async operations
- Dynamic tool management documentation and examples

### Changed
- **Breaking**: Settings API simplified to only accept LM instances (`configure(lm=...)` instead of `configure(api_key=..., model=...)`)
- **Breaking**: Restructured ReAct trajectory as `list[Episode]` with typed fields (`thought`, `tool_name`, `tool_args`, `observation`)
- Renamed `_aexecute` to `aexecute` for public API
- Split `tool.py` into `tool/` package
- Split `predict.py` into focused submodules
- Split `utils` into package with `async_support.py`, `formatting.py`, `schema.py`
- Removed `GroqLM` class in favor of OpenAI-compatible API
- Made `emit_event` synchronous with `put_nowait`

### Fixed
- Streaming queue consumption prioritization
- Tool callback timing moved to actual execution point
- React tool execution `UnboundLocalError`
- Handle models without provider prefix in `_clean_model_name`

## [0.1.2] - 2025-10-24

### Added
- Codecov integration with OIDC tokenless upload
- Pre-release check command

### Fixed
- Documentation badge URLs and absolute paths

## [0.1.1] - 2025-10-24

### Added
- ReAct module with native tool calling and human-in-the-loop flow
- ChainOfThought module for step-by-step reasoning
- History class for conversation management
- Context manager for thread-safe settings overrides
- Automatic tool calling with streaming support
- Optional tool execution with `auto_execute_tools` parameter
- Architectural Decision Records (ADRs)
- GitHub Actions CI/CD workflows

### Changed
- Converted `module.py` into `module/` package
- Standardized module execution with `_aexecute` pattern
- Improved field parsing with regex-based extraction

## [0.1.0] - 2025-10-24

### Added
- Core Predict module with streaming support
- Signature system for defining inputs/outputs
- ChatAdapter for OpenAI integration
- ChainOfThought module for step-by-step reasoning
- Tool calling with `@tool` decorator
- Automatic multi-turn conversation handling
- Context-aware settings management
- Comprehensive test suite (90%+ coverage)
- Documentation with MkDocs

### Features
- Async-first architecture with sync wrappers
- Streaming with field-level chunking
- Event system for custom streaming events
- Native OpenAI tool calling integration
- Pydantic-based schemas
- Type-safe API with full type hints
- Comprehensive test suite
- Documentation with MkDocs
32 changes: 27 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Created a minimal DSPy-inspired library focused on:
- This reduces complexity and leverages OpenAI's optimized tool calling

2. **Minimal Dependencies**
- Only `openai` and `pydantic` in core dependencies
- Core dependencies: `openai`, `pydantic`, `tenacity`, `jiter`, `regex`
- Keeps the library lightweight and maintainable
- Reduces potential dependency conflicts

Expand All @@ -48,8 +48,30 @@ udspy/
├── src/udspy/ # Core library code
│ ├── signature.py # Signature, InputField, OutputField
│ ├── adapter.py # ChatAdapter for formatting
│ ├── module.py # Module, Predict abstractions
│ └── streaming.py # Streaming support
│ ├── module/ # Module abstractions (package)
│ │ ├── base.py # Module base class
│ │ ├── predict.py # Predict module
│ │ ├── chain_of_thought.py # ChainOfThought module
│ │ ├── react.py # ReAct agent module
│ │ └── callbacks.py # Module callback system
│ ├── lm/ # Language model abstraction (package)
│ │ ├── base.py # LM abstract base class
│ │ ├── openai.py # OpenAI implementation
│ │ └── factory.py # LM factory with provider registry
│ ├── tool/ # Tool system (package)
│ │ ├── tool.py # Tool class and ToolCall
│ │ ├── decorator.py # @tool decorator
│ │ └── types.py # Tool-related types
│ ├── utils/ # Utilities (package)
│ │ ├── async_support.py # Async helpers
│ │ ├── formatting.py # Error formatting
│ │ └── schema.py # Schema utilities
│ ├── streaming.py # Streaming support
│ ├── settings.py # Global settings with context manager
│ ├── history.py # Conversation history
│ ├── callback.py # Callback/telemetry system
│ ├── confirmation.py # Human-in-the-loop confirmation
│ └── exceptions.py # Custom exceptions
├── tests/ # Pytest tests
├── docs/ # MkDocs documentation
├── examples/ # Usage examples
Expand Down Expand Up @@ -134,7 +156,7 @@ result = predictor(question="...") # Uses global-key and gpt-4o-mini
async def handle_user(user):
user_lm = LM(model="gpt-4o-mini", api_key=user.api_key)
with udspy.settings.context(lm=user_lm):
async for chunk in streaming_predictor.stream(...):
async for chunk in streaming_predictor.astream(...):
yield chunk
```

Expand Down Expand Up @@ -275,7 +297,7 @@ Feature is additive - no migration needed.
For complete details on recent architectural decisions, see the full ADRs in [docs/architecture/decisions.md](docs/architecture/decisions.md):

- **ADR-008: Module Callbacks and Dynamic Tool Management** - `@module_callback` decorator for runtime tool loading
- **ADR-009: History Management with System Prompts** - Dedicated `system_prompt` property ensures proper positioning
- **ADR-009: History Management with System Prompts** - `set_system_message()` method ensures system prompt is always at position 0
- **ADR-010: LM Callable Interface with String Prompts** - Simple `lm("prompt")` returns text directly

---
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ If resource constraints aren't a concern for your use case, consider [DSPy](http
- **Optional Tool Execution**: Control whether tools execute automatically or return for manual handling
- **Module Abstraction**: Compose LLM calls with reusable modules (`Predict`, `ChainOfThought`, `ReAct`)
- **Streaming Support**: Stream reasoning and output fields incrementally with async generators
- **Minimal Dependencies**: Only requires `openai` and `pydantic` (~10MB total footprint)
- **Minimal Dependencies**: Only requires `openai`, `pydantic`, `tenacity`, `jiter`, and `regex` (~10MB total footprint)

## Installation

Expand Down Expand Up @@ -137,7 +137,7 @@ print(result.answer) # Tools are automatically executed
### With Human-in-the-Loop

```python
from udspy import ReAct, HumanInTheLoopRequired, tool
from udspy import ReAct, ConfirmationRequired, tool
from pydantic import Field
import os

Expand All @@ -159,7 +159,7 @@ agent = ReAct(FileTask, tools=[delete_file])

try:
result = agent(request="Delete /tmp/old_data.txt")
except HumanInTheLoopRequired as e:
except ConfirmationRequired as e:
print(f"Agent asks: {e.question}")
# User confirms: "yes", "no", or provides feedback
result = agent.resume("yes", e)
Expand Down
52 changes: 24 additions & 28 deletions docs/architecture/callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,30 +91,16 @@ Configure callbacks globally via `settings.configure()`:

```python
import udspy
from udspy import LM

lm = LM(model="gpt-4o-mini", api_key="sk-...")
callback = LoggingCallback()
udspy.settings.configure(
api_key="sk-...",
model="gpt-4o-mini",
lm=lm,
callbacks=[callback] # Applied to all modules and tools
)
```

### Per-Module Callbacks

Configure callbacks for specific module instances:

```python
from udspy import Predict, Signature, InputField, OutputField

class QA(Signature):
question: str = InputField()
answer: str = OutputField()

# This callback only applies to this predictor instance
predictor = Predict(QA, callbacks=[LoggingCallback()])
```

### Context-Specific Callbacks

Use temporary callbacks within a context:
Expand All @@ -127,15 +113,20 @@ with udspy.settings.context(callbacks=[DebugCallback()]):

### Combining Callbacks

Callbacks are combined from multiple sources:
Use global callbacks for persistent monitoring and context callbacks for temporary overrides:

```python
# Global callbacks + instance callbacks are all executed
udspy.settings.configure(callbacks=[GlobalCallback()])
predictor = Predict(QA, callbacks=[InstanceCallback()])
from udspy import LM

lm = LM(model="gpt-4o-mini", api_key="sk-...")
udspy.settings.configure(lm=lm, callbacks=[GlobalCallback()])

# Both GlobalCallback and InstanceCallback will be invoked
# GlobalCallback is invoked
result = predictor(question="...")

# Context temporarily overrides with different callbacks
with udspy.settings.context(callbacks=[DebugCallback()]):
result = predictor(question="...") # Only DebugCallback is invoked
```

## Callback Execution Flow
Expand Down Expand Up @@ -219,9 +210,11 @@ Tools like Opik and MLflow that provide DSPy callbacks will work with udspy:
```python
# Example with Opik (hypothetical - check Opik docs for actual API)
from opik import OpikCallback
from udspy import LM

lm = LM(model="gpt-4o-mini", api_key="sk-...")
udspy.settings.configure(
api_key="sk-...",
lm=lm,
callbacks=[OpikCallback(project="my-project")]
)

Expand All @@ -248,11 +241,12 @@ udspy.settings.configure(callbacks=[
])
```

### 2. Use Instance Callbacks for Specific Monitoring
### 2. Use Context Callbacks for Specific Monitoring

```python
# Monitor only critical paths
critical_predictor = Predict(ImportantTask, callbacks=[AlertCallback()])
# Monitor only critical paths with context-scoped callbacks
with udspy.settings.context(callbacks=[AlertCallback()]):
result = critical_predictor(question="...")
```

### 3. Use Context Callbacks for Debugging
Expand Down Expand Up @@ -342,9 +336,11 @@ If you're using DSPy callbacks, migration is straightforward:
import dspy
dspy.settings.configure(callbacks=[MyCallback()])

# udspy code - exactly the same!
# udspy code - same callback interface!
import udspy
udspy.settings.configure(callbacks=[MyCallback()])
from udspy import LM
lm = LM(model="gpt-4o-mini", api_key="sk-...")
udspy.settings.configure(lm=lm, callbacks=[MyCallback()])
```

The callback interface is identical, so existing DSPy callbacks should work without modification.
Loading
Loading