Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
fd6815b
feat: implement streaming support for typed generator outputs and add…
battmanux Apr 19, 2026
126f401
feat: implement streaming support for iterative emulation via generat…
battmanux Apr 19, 2026
8c91a3a
cicd: gpt-4.1 is becoming very slow. use a better test for uncertaint…
battmanux Apr 19, 2026
81b07ab
fix: simple test for Iterator
battmanux Apr 19, 2026
ac695d1
Merge remote-tracking branch 'origin/main' into streaming_support
battmanux Apr 19, 2026
38ce0ae
feat: add dataclass support to generator emulation and update analyze…
battmanux Apr 19, 2026
aeb500f
feat: initialize and update inspection logs with streaming response c…
battmanux Apr 19, 2026
bd425e5
feat: replace BatchDataContext with gather_data utility for declarati…
battmanux Apr 19, 2026
29d0e60
test: add comprehensive unit and functional tests for gather_data and…
battmanux Apr 19, 2026
cdda8af
refactor: remove BatchDataContext and expose gather_data utility func…
battmanux Apr 19, 2026
92e4ad4
refactor: rename emulate_iterator to emulate_variants and improve ret…
battmanux Apr 19, 2026
7888337
fix: meta prompt shall not ask for python bloc in python bloc
battmanux Apr 21, 2026
cd3053d
cicd: version
battmanux Apr 21, 2026
42e5a17
docs: document the Streaming Upgrade and update local test examples t…
battmanux Apr 22, 2026
1b241a3
Merge remote-tracking branch 'origin/main' into streaming_support
battmanux Apr 22, 2026
a6dafda
refactor: improve parsing error handling in primitives and expand gua…
battmanux Apr 24, 2026
928706c
feat: better error messgages when llm does not respect annotated types
battmanux Apr 24, 2026
7bca195
doc: explain how to disaable thinking with ollama
battmanux Apr 24, 2026
c8a9934
feat: add assistant_starts_with as a force_llm_args key
battmanux Apr 24, 2026
049f5a7
refacto: Méta-Prompts optimisés
battmanux Apr 24, 2026
4c7a5c3
feat: Configuration flexible : Ajout de OPENHOSTA_DEFAULT_MODEL_API_P…
battmanux Apr 24, 2026
afb547b
feat: Introspection des Callables
battmanux Apr 24, 2026
6fee838
tests: add tests for new features
battmanux Apr 24, 2026
56354ff
docs: Document env variables for production
battmanux Apr 24, 2026
658313c
refacto: Unification des blocs de code dans le template.
battmanux Apr 24, 2026
76605d6
fix: Ajout des caches spécifiques aux factories et simplification de …
battmanux Apr 24, 2026
79a804d
fix: Ajout du cache centralisé pour unifier les Guarded types
battmanux Apr 24, 2026
19bffc2
refactor: implement robust LLM response cleaning and add comprehensiv…
battmanux Apr 24, 2026
c08220d
feat: implement inspection support for Guarded types and add debuggin…
battmanux Apr 25, 2026
97e5bb5
Merge remote-tracking branch 'origin/fix_meta_prompt_for_callables' i…
battmanux Apr 25, 2026
f0393bd
fix: Guarded[T] shall not be visible in metaprompt
battmanux Apr 28, 2026
5e809a0
fix: Enum strip missing
battmanux Apr 28, 2026
4b1d26a
Merge branch 'main' into fix_better_type_description_in_meta_prompt
battmanux Apr 28, 2026
0d03fec
fix: adjust tests, fix heuristic in not str as input
battmanux Apr 28, 2026
f74b82b
cicd: change version to v4.3.0
battmanux Apr 28, 2026
1489727
Merge remote-tracking branch 'origin/fix_meta_prompt_for_callables' i…
battmanux Apr 28, 2026
ba35586
Merge remote-tracking branch 'origin/fix_enum_with_quotes' into fix_b…
battmanux Apr 28, 2026
c06242f
fix: TypeAlias missing after merge conflict
battmanux Apr 28, 2026
6c02c61
fix: Guarded shall be declared as a class. remove safe() from functio…
battmanux Apr 28, 2026
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
9 changes: 9 additions & 0 deletions .agents/rules/use-venv.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
trigger: always_on
---

I have created a .venv in OpenHosta.git/tests using uv
I then chnaged directory to tests/ and I installed the local clone of OpenHosta using uv pip install -e ..
I write default LLM credentials in tests/.env so that OpenHosta reads it when I start the python interpreter from tests/ folder

When running a python code you must cd to tests folder and use python from its .venv that was created using uv
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ capabilities.yaml
final_capabilities.yaml
**/.scannerwork
logs/
.supported_providers.yaml
.supported_providers.yaml
scratch/*
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ print(translate("Hello World!", "French"))
|---------|-------------|
| [`emulate`](https://github.com/hand-e-fr/OpenHosta/blob/main/docs/core_functions.md) | AI-implemented functions from docstrings |
| [`emulate_async`](https://github.com/hand-e-fr/OpenHosta/blob/main/docs/core_functions.md) | Non-blocking async variant for concurrency |
| [`emulate_iterator`](https://github.com/hand-e-fr/OpenHosta/blob/main/docs/core_functions.md) | Streaming results via lazy generators |
| [`emulate_variants`](https://github.com/hand-e-fr/OpenHosta/blob/main/docs/core_functions.md) | Streaming results via lazy generators |
| [`closure`](https://github.com/hand-e-fr/OpenHosta/blob/main/docs/core_functions.md) | Semantic lambda functions |
| [`test`](https://github.com/hand-e-fr/OpenHosta/blob/main/docs/core_functions.md) | Fuzzy logic / semantic boolean tests |
| [Types & Pydantic](https://github.com/hand-e-fr/OpenHosta/blob/main/docs/types_and_pydantic.md) | `int`, `dict`, `Enum`, `dataclass`, `Pydantic`, `Callable`… |
Expand Down
30 changes: 21 additions & 9 deletions docs/core_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def translate(text: str, language: str) -> str:
print(translate("Hello", "French"))
```

> [!TIP]
> **The Streaming Upgrade:** If you change the return type from `list[T]` to `Iterator[T]`, OpenHosta automatically switches to streaming mode, allowing you to process items as they are generated by the model.

## `emulate_async`
Useful for web applications, heavy IO, or executing multiple LLM calls in parallel without blocking the main event loop.

Expand All @@ -29,20 +32,29 @@ async def capitalize_cities(sentence: str) -> str:
print(asyncio.run(capitalize_cities("je suis allé à paris")))
```

## `emulate_iterator`
Returns a lazily evaluated generator/iterator. It yields elements one-by-one directly from the underlying LLM streams, vastly reducing latency for list generations.
## `emulate_variants`
Explores the LLM's probability distribution to generate independent, alternative responses (variants) based on token logprobs. It is ideal for exploring uncertainty or generating diverse candidates from a single prompt.

> Note: tested mainly with qwen3:8b-instruct served by ollama
It automatically adapts to the return type annotation:
- If `-> list[T]`: Returns a fully resolved list of variants.
- If `-> Iterator[T]`: Returns a generator that yields variants as they are found.

```python
from OpenHosta import emulate_iterator
from typing import Iterator
from OpenHosta import emulate_variants

# Returns a list of variants once all are found
def list_variants(topic: str) -> list[str]:
"""Suggest three alternative creative names for a project."""
return emulate_variants(min_probability=1e-2)

def generate_ideas(topic: str) -> list[str]:
"""Yield multiple creative ideas based on the topic."""
return emulate_iterator()
# Yields variants one by one
def stream_variants(topic: str) -> Iterator[str]:
"""Yield multiple alternative titles based on the topic."""
yield from emulate_variants(min_probability=1e-2)

for idea in generate_ideas("Open Source Marketing"):
print(idea) # Starts printing before the entire list is fully generated
for variant in stream_variants("Open Source Marketing"):
print(variant)
```

## `closure`
Expand Down
85 changes: 85 additions & 0 deletions docs/debugging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# 🛠️ Debugging and Inspection

OpenHosta provides powerful tools to inspect how your LLM functions behave and to verify the quality of the generated data.

## I. Using Guarded Types for Introspection

By default, `emulate()` returns native Python types (int, str, list, etc.). While convenient, these types lose the "provenance" metadata (the prompt that produced them, the raw LLM response, etc.).

To keep this metadata, use the `Guarded` wrapper or a specific `Guarded` type in your annotation:

```python
from OpenHosta import emulate, Guarded

# Native return (no metadata)
def get_age(name: str) -> int:
return emulate()

# Guarded return (metadata preserved)
def get_age_guarded(name: str) -> Guarded[int]:
return emulate()
```

### Why use Guarded types?
1. **Traceability**: You can see the exact conversation that produced the value.
2. **Uncertainty**: You can check if the LLM was "confident" about the result.
3. **Stability**: You can get a "clean" version of the data without LLM artifacts.

---

## II. Inspection Functions

Once you have a Guarded value, you can use the following functions:

### 1. `conversation(value)`
Prints the full conversation (System prompt + User prompt) that led to this value.

```python
age = get_age_guarded("John")
conversation(age)
```

### 2. `readable(value)`
Returns a human-friendly string representation of the value.
- It removes LLM artifacts (comments, extra text).
- It pretty-prints complex structures (lists, dicts).
- It is **stable**: `GuardedType(readable(val))` is guaranteed to produce the same value as the original.

### 3. `markdown(value)`
Same as `readable()`, but wrapped in Markdown code blocks for better rendering in reports or other LLMs.

---

## III. Comparison Table

| Feature | `str(val)` | `repr(val)` | `readable(val)` | `markdown(val)` |
| :--- | :--- | :--- | :--- | :--- |
| **Target** | Programmatic | Developers | Humans / Logs | Reports / LLMs |
| **Quotes** | No (for str) | Yes (for str) | No | Block if complex |
| **Format** | Default Python | Technical | Pretty-printed | Markdown Block |
| **Stability** | Yes | Yes | **Guaranteed** | Yes |

---

## IV. Debugging Workflows

### Inspecting a Function (Standard)
If you don't want to change your return types, you can always inspect the *function* itself to see its *last* execution:

```python
from OpenHosta import print_last_prompt

age = get_age("John")
print_last_prompt(get_age)
```

### Inspecting a Value (Recommended for Loops/Batch)
If you are calling a function many times, `print_last_prompt(func)` only shows the last one. Using `Guarded` types allows you to inspect *any* result at any time:

```python
results = [get_age_guarded(n) for n in names]

# Later, inspect a specific result
conversation(results[5])
print(f"Readable value: {readable(results[5])}")
```
2 changes: 1 addition & 1 deletion docs/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Take off your conceptual hat and observe OpenHosta in action through functional
- 📚 [**Text Classification:**](examples/text_classification.md) Sorting streams of text directly into rigidly typed `Enum` states.
- 🗃️ [**Data Extraction:**](examples/data_extraction.md) Populating massive `Dataclasses` and `Pydantic` modules straight from unstructured text blobs.
- 👁️ [**Local OCR with Ollama:**](examples/ocr_local_ollama.md) Passing images using `PIL.Image` directly into `emulate`, performing OCR securely and locally using `glm-ocr`.
- ⚡ [**Parallel Processing:**](examples/parallel_processing.md) Running asynchronous workloads, parsing dataclasses like invoices, and batching prompts.
- ⚡ [**Parallel Processing:**](parallel_processing.md) Running asynchronous workloads, parsing dataclasses like invoices, and batching prompts.
- 🌊 [**Streaming & Iteration:**](streaming.md) Receiving results token-by-token or item-by-item to improve UX and handle long responses.

---
Expand Down
83 changes: 0 additions & 83 deletions docs/examples/parallel_processing.md

This file was deleted.

6 changes: 6 additions & 0 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ from OpenHosta import config
config.DefaultModel.base_url = "http://localhost:11434/v1"
config.DefaultModel.model_name = "qwen3.5:4b"
config.DefaultModel.api_key = "not used by ollama local api"

# Tip: Disable reasoning/thinking to speed up Qwen
config.DefaultModel.api_parameters |= {"reasoning": {"effort": "none"}}
```

## Supported Environment Variables
Expand All @@ -28,6 +31,9 @@ OPENHOSTA_DEFAULT_MODEL_NAME="gpt-4.1" # Default
OPENHOSTA_DEFAULT_MODEL_TEMPERATURE=0.7 # Optional
OPENHOSTA_DEFAULT_MODEL_SEED=42 # Optional. Deterministic for local LLMs
OPENHOSTA_RATE_LIMIT_WAIT_TIME=60 # Optional
OPENHOSTA_AUDIT_MODE=False # Optional. Enable structured audit logs
OPENHOSTA_SILENCE_ENV_WARNING=False # Optional. Silence .env missing warnings
OPENHOSTA_DEFAULT_MODEL_API_PARAMETERS='{"top_k":5}' # Optional. JSON string for extra model params
```

### *Legal Framework*
Expand Down
70 changes: 70 additions & 0 deletions docs/guarded.md
Original file line number Diff line number Diff line change
Expand Up @@ -1083,3 +1083,73 @@ port = GuardedInt(config["port"]) # 8080
- `subclassablecollections.py` - Collections and dataclasses
- `subclassableclasses.py` - GuardedEnum
- `resolver.py` - Type resolution

---

## X. Direct Usage: Parsing LLM Output

While OpenHosta usually handles parsing automatically via `emulate()`, you can use Guarded types directly to parse raw strings from any LLM client (OpenAI, Anthropic, LangChain, etc.).

### 10.1 Using TypeResolver

The most robust way to parse a string into a specific type is to use `TypeResolver.resolve()`. It handles all Python annotations (List, Dict, Dataclasses, etc.).

```python
from typing import List
from OpenHosta.guarded.resolver import TypeResolver

# 1. Define your expected type
MyType = List[int]

# 2. Get the LLM output (raw string)
raw_output = "I found these numbers: [10, 20, 30] # and some noise"

# 3. Resolve the type and parse
guarded_type = TypeResolver.resolve(MyType)
result = guarded_type.attempt(raw_output)

if result.success:
data = result.data # [10, 20, 30]
print(f"Parsed {len(data)} items with uncertainty {result.uncertainty}")
else:
print(f"Parsing failed: {result.error_message}")
```

### 10.2 Integrating with a custom Model call

If you are using an OpenHosta `Model` instance directly:

```python
from OpenHosta.defaults import config
from OpenHosta.guarded.resolver import TypeResolver

model = config.DefaultModel
messages = [{"role": "user", "content": "Return the price of BTC as a float."}]

# Call the LLM
response_dict = model.api_call(messages)
raw_content = model.get_response_content(response_dict)

# Parse directly
price = TypeResolver.resolve(float).attempt(raw_content).data

print(f"Current price: {price}")
```

### 10.3 Why use `attempt()`?

Using `attempt()` instead of direct instantiation (e.g., `GuardedInt(val)`) gives you more control:

1. **No Exceptions**: It returns a `CastingResult` instead of raising `ValueError`, making it safer for production pipelines.
2. **Metadata**: You get the `uncertainty` and `abstraction_level` (native vs heuristic).
3. **Configurable Tolerance**: You can specify how "creative" the parser should be.

```python
from OpenHosta.guarded import GuardedInt, Tolerance

result = GuardedInt.attempt("42 # with comments", tolerance=Tolerance.STRICT)
# result.success will be False (STRICT only accepts clean "42")

result = GuardedInt.attempt("42 # with comments", tolerance=Tolerance.FLEXIBLE)
# result.success will be True (FLEXIBLE accepts heuristic cleaning)
```
16 changes: 14 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ Welcome to the **OpenHosta** documentation. OpenHosta is the semantic layer for
Set up your environment, configure a local or remote model, and run your first `emulate()` call.

### ⚙️ [Core Functions](core_functions.md)
Learn about `emulate`, `emulate_async`, `emulate_iterator`, `closure`, `ask`, and `test`.
Learn about `emulate`, `emulate_async`, `emulate_variants`, `closure`, `ask`, and `test`.

### 🔄 [Streaming & Iterators](streaming.md)
Stream raw tokens with `ask_stream`, or yield structured Python objects one-by-one with `Iterator` return types and `emulate_variants`.

### 🔧 [Models & Setup](models_and_setup.md)
Connect any OpenAI-compatible endpoint (Ollama, vLLM, Azure OpenAI), customize prompts, enable audit mode, and track costs.
Expand All @@ -23,9 +26,18 @@ OpenHosta natively supports `int`, `str`, `List`, `Dict`, `Enum`, `dataclass`, `
### 🛡️ [Safe Context & Error Handling](safe_context_and_uncertainty.md)
Handle uncertainty, catch ambiguous LLM responses, and build robust production workflows.

### ⚡ [Parallel Processing](parallel_processing.md)
Simplify concurrent execution of `emulate_async` with the `gather_data` batching utility to parse vast amount of items concurrently.

### 📐 [Guarded Types](guarded.md)
Deep dive into OpenHosta's type validation and conversion system with configurable tolerance.

### 🛠️ [Debugging & Inspection](debugging.md)
Learn how to use `conversation()`, `readable()`, and `markdown()` to inspect LLM outputs and debug your semantic code.

### 🛡️ [Production & Auditing](production.md)
Learn how to deploy OpenHosta at scale, enable audit logging for compliance, and track token usage in production.

---

## Cookbook
Expand All @@ -35,7 +47,7 @@ Deep dive into OpenHosta's type validation and conversion system with configurab
| 📚 [Text Classification](examples/text_classification.md) | Classify text into `Enum` states |
| 🗃️ [Data Extraction](examples/data_extraction.md) | Populate `dataclass` / `Pydantic` from unstructured text |
| 👁️ [Local OCR](examples/ocr_local_ollama.md) | Image processing with `PIL.Image` + Ollama |
| ⚡ [Parallel Processing](examples/parallel_processing.md) | Async batch workloads with `emulate_async` |
| 🔄 [Streaming & Iterators](streaming.md) | Stream tokens or yield typed objects with `Iterator` |

---

Expand Down
Loading
Loading