windlass.interfaces¶
interfaces
¶
Windlass interfaces — the contracts every component implements.
This package is the architectural heart of the framework. Everything else depends on these abstractions and nothing depends on a concrete provider, which is what the dependency-inversion principle buys us in practice:
- Swapping OpenAI for Ollama is a string change, not a refactor.
- A custom retriever written in a user's repo is indistinguishable from a built-in one.
- The whole framework can be exercised in tests with fakes that implement these same interfaces.
There is exactly one interface per component kind:
============ =========================================================
Kind Interface
============ =========================================================
llm :class:~windlass.interfaces.llm.LLM
embedding :class:~windlass.interfaces.embedding.Embedder
loader :class:~windlass.interfaces.loader.Loader
preprocessor :class:~windlass.interfaces.preprocessor.Preprocessor
chunker :class:~windlass.interfaces.chunker.Chunker
retriever :class:~windlass.interfaces.retriever.Retriever
vectordb :class:~windlass.interfaces.vectordb.VectorStore
memory :class:~windlass.interfaces.memory.Memory
guardrail :class:~windlass.interfaces.guardrail.Guardrail
evaluator :class:~windlass.interfaces.evaluator.Evaluator
tracer :class:~windlass.interfaces.tracer.Tracer
tool :class:~windlass.interfaces.tool.Tool
mcp :class:~windlass.interfaces.mcp.MCPClient
============ =========================================================
Every one of them shares :class:~windlass.interfaces.base.Component, which
supplies naming, configuration, lifecycle and the native() escape hatch.
Component
¶
Bases: SupportsNative
Abstract base for every registrable Windlass component.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str | None
|
Identifier used in traces and error messages. Defaults to the class's registered name, or its class name. |
None
|
**config
|
Any
|
Arbitrary component configuration, kept on :attr: |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
name |
The component's identifier. |
|
config |
dict[str, Any]
|
The configuration it was constructed with. |
Example
class Upper(Component): ... def apply(self, text: str) -> str: ... return text.upper() Upper(name="upper").name 'upper'
Source code in src\windlass\interfaces\base.py
option
¶
Read a configuration value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Configuration key. |
required |
default
|
Any
|
Returned when the key is absent. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The configured value or |
Source code in src\windlass\interfaces\base.py
require_option
¶
Read a configuration value that must be present.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Configuration key. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The configured value. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
When the key is missing or |
Source code in src\windlass\interfaces\base.py
with_config
¶
Return a new component of the same type with merged configuration.
Components are treated as immutable once built; reconfiguring produces a copy so a shared component cannot be mutated out from under another pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**overrides
|
Any
|
Configuration to merge over the current values. |
{}
|
Returns:
| Type | Description |
|---|---|
Component
|
A new instance of the same class. |
Source code in src\windlass\interfaces\base.py
aclose
async
¶
Release resources held by the component.
The default is a no-op. Adapters holding sockets, file handles or thread pools should override it. Safe to call more than once.
close
¶
native
¶
Return the wrapped provider object.
The default returns self, which is correct for pure-Python
components that wrap nothing.
describe
¶
Return a JSON-safe summary of this component.
Used by tracing, by Pipeline.describe() and by the CLI.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict with |
Source code in src\windlass\interfaces\base.py
SupportsNative
¶
Bases: ABC
Mixin declaring the Level 3 escape hatch.
Any adapter wrapping a third-party object should return it from
:meth:native so advanced users can reach past Windlass without forking it.
native
abstractmethod
¶
Return the underlying provider object.
Returns:
| Type | Description |
|---|---|
Any
|
The wrapped SDK client, index, graph or handle. Adapters with no |
Any
|
third-party object behind them return |
Example
from windlass.providers.llm.fake import FakeLLM FakeLLM().native() is not None True
Source code in src\windlass\interfaces\base.py
Chunker
¶
Chunker(
*,
chunk_size: int = 1000,
overlap: int | None = None,
min_chunk_size: int = 50,
keep_separator: bool = True,
name: str | None = None,
**config: Any
)
Bases: Component
Abstract text splitter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chunk_size
|
int
|
Target size of each chunk, measured in :attr: |
1000
|
overlap
|
int | None
|
How much each chunk repeats from the previous one. Overlap
keeps a fact that straddles a boundary retrievable from either side.
|
None
|
min_chunk_size
|
int
|
Chunks shorter than this are merged into their neighbour rather than emitted — stray one-line fragments pollute retrieval. |
50
|
keep_separator
|
bool
|
Whether the split separator stays in the output. |
True
|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Strategy-specific options. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
unit |
str
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If an explicit |
Example
Implementing a chunker takes one method::
class LineChunker(Chunker):
provider_name = "line"
def split_text(self, text: str) -> list[str]:
return [ln for ln in text.splitlines() if ln.strip()]
Source code in src\windlass\interfaces\chunker.py
split_text
abstractmethod
¶
Split raw text into chunk strings.
The only method a strategy must implement. Return the pieces in reading
order; the base class turns them into :class:Chunk objects with ids,
offsets and inherited metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to split. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
Chunk strings in document order. |
Source code in src\windlass\interfaces\chunker.py
asplit_text
async
¶
Async :meth:split_text.
Override this instead when your strategy needs to await something — the semantic chunker calls an embedding model here.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to split. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
Chunk strings in document order. |
Source code in src\windlass\interfaces\chunker.py
achunk_document
async
¶
Split one document into chunks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
Document
|
The document to split. |
required |
Returns:
| Type | Description |
|---|---|
list[Chunk]
|
Chunks carrying the document's metadata plus |
list[Chunk]
|
|
Source code in src\windlass\interfaces\chunker.py
achunk
async
¶
achunk(
documents: Sequence[Document] | Document, *, concurrency: int | None = None
) -> list[Chunk]
Split many documents concurrently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
documents
|
Sequence[Document] | Document
|
One document or a sequence of them. |
required |
concurrency
|
int | None
|
Maximum simultaneous splits. Defaults to the global
|
None
|
Returns:
| Type | Description |
|---|---|
list[Chunk]
|
All chunks, grouped by source document in input order. |
Performance
CPU-bound strategies see little benefit from concurrency; strategies
that await a model (semantic, contextual) see a lot.
Source code in src\windlass\interfaces\chunker.py
chunk
¶
chunk_text
¶
Split a bare string, without constructing a Document first.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to split. |
required |
metadata
|
dict[str, Any] | None
|
Metadata copied onto every chunk. |
None
|
Returns:
| Type | Description |
|---|---|
list[Chunk]
|
The resulting chunks. |
Example
from windlass.providers.chunkers.recursive import RecursiveChunker RecursiveChunker(chunk_size=100, overlap=10).chunk_text("hello")[0].content 'hello'
Source code in src\windlass\interfaces\chunker.py
Embedder
¶
Embedder(
model: str = "",
*,
dimensions: int | None = None,
batch_size: int | None = None,
normalize: bool = True,
cache: Cache | None = None,
name: str | None = None,
**config: Any
)
Bases: Component
Abstract text-embedding model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
Model identifier passed to the provider. |
''
|
dimensions
|
int | None
|
Output dimensionality. Providers that expose a native value
should override :meth: |
None
|
batch_size
|
int | None
|
How many texts to send per provider request. |
None
|
normalize
|
bool
|
L2-normalise every vector. Recommended: it makes cosine similarity a plain dot product and matches what FAISS expects. |
True
|
cache
|
Cache | None
|
Optional cache for embeddings. Embeddings are deterministic, so caching them is always safe. |
None
|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Provider-specific options. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
model |
Configured model identifier. |
|
batch_size |
int
|
Configured request batch size. |
normalize |
bool
|
Whether vectors are normalised on the way out. |
Example
Implementing a provider takes one method::
class MyEmbedder(Embedder):
provider_name = "mine"
async def aembed_texts(self, texts, *, kind="document"):
return [await my_sdk.embed(t) for t in texts]
Source code in src\windlass\interfaces\embedding.py
default_model
classmethod
¶
aembed_texts
abstractmethod
async
¶
Embed a batch of texts.
This is the only method a provider must implement. It receives at most
:attr:batch_size texts, already prefixed for kind.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
list[str]
|
The texts to embed. Never empty. |
required |
kind
|
str
|
|
'document'
|
Returns:
| Type | Description |
|---|---|
list[list[float]]
|
One vector per input, in the same order. |
Raises:
| Type | Description |
|---|---|
ProviderError
|
For any provider-side failure. |
Source code in src\windlass\interfaces\embedding.py
dimension
¶
Return the vector dimensionality.
Determined from configuration when possible, otherwise by embedding a one-character probe and measuring the result. The probe runs at most once per instance.
Returns:
| Type | Description |
|---|---|
int
|
The number of components in each vector. |
Example
from windlass.providers.embeddings.hash import HashEmbedder HashEmbedder(dimensions=64).dimension() 64
Source code in src\windlass\interfaces\embedding.py
aembed
async
¶
aembed(
texts: Sequence[str], *, kind: str = "document", concurrency: int | None = None
) -> list[list[float]]
Embed many texts with batching, bounded concurrency and caching.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
texts
|
Sequence[str]
|
The texts to embed. |
required |
kind
|
str
|
|
'document'
|
concurrency
|
int | None
|
Maximum simultaneous provider requests. Defaults to the
global |
None
|
Returns:
| Type | Description |
|---|---|
list[list[float]]
|
One vector per input, in input order. |
Raises:
| Type | Description |
|---|---|
ProviderError
|
For any provider-side failure. |
ValueError
|
If |
Performance
Texts are grouped into :attr:batch_size batches and the batches
run concurrently up to concurrency. Cached texts never reach the
provider, so re-ingesting a mostly-unchanged corpus is cheap.
Example
import asyncio from windlass.providers.embeddings.hash import HashEmbedder len(asyncio.run(HashEmbedder(dimensions=8).aembed(["a", "b"]))) 2
Source code in src\windlass\interfaces\embedding.py
embed
¶
aembed_one
async
¶
Embed a single text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The text to embed. |
required |
kind
|
str
|
|
'document'
|
Returns:
| Type | Description |
|---|---|
list[float]
|
One vector. |
Source code in src\windlass\interfaces\embedding.py
embed_one
¶
aembed_query
async
¶
embed_query
¶
EvalSample
¶
Bases: WindlassModel
One evaluated interaction.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Sample identifier, used to correlate results. |
question |
str
|
The user's input. |
answer |
str
|
What the system produced. |
contexts |
list[str]
|
Retrieved texts that were placed in the prompt. Required by faithfulness and context-precision metrics. |
reference |
str
|
The ground-truth answer, when you have one. |
reference_contexts |
list[str]
|
The ideal contexts, for retrieval recall metrics. |
metadata |
dict[str, Any]
|
Anything else worth slicing results by (tenant, model, ...). |
Example
EvalSample(question="q", answer="a").id != "" True
from_answer
classmethod
¶
Build a sample straight from a RAG answer.
This is the ergonomic path: run your pipeline, feed the answers to the evaluator, done.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
answer
|
RAGAnswer
|
What :meth: |
required |
reference
|
str
|
Ground truth, when available. |
''
|
Returns:
| Type | Description |
|---|---|
EvalSample
|
A populated sample. |
Example
from windlass.core.types import RAGAnswer EvalSample.from_answer(RAGAnswer(answer="a", question="q")).question 'q'
Source code in src\windlass\interfaces\evaluator.py
Evaluator
¶
Evaluator(
*,
metrics: Sequence[str] | None = None,
threshold: float = 0.5,
llm: Any = None,
concurrency: int | None = None,
name: str | None = None,
**config: Any
)
Bases: Component
Abstract evaluation backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metrics
|
Sequence[str] | None
|
Metric names to compute. Which names are valid depends on the
backend; ask it with :meth: |
None
|
threshold
|
float
|
Score at or above which a result counts as passing. |
0.5
|
llm
|
Any
|
Judge model for metrics that need one. Many quality metrics are themselves LLM calls. |
None
|
concurrency
|
int | None
|
Maximum simultaneous sample evaluations. |
None
|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Backend-specific options. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
metrics |
list[str]
|
The configured metric names. |
threshold |
The configured pass threshold. |
Example
Implementing an evaluator takes one method::
class LengthEvaluator(Evaluator):
provider_name = "length"
async def aevaluate_sample(self, sample):
score = min(1.0, len(sample.answer) / 100)
return [EvaluationResult(metric="length", score=score)]
Source code in src\windlass\interfaces\evaluator.py
default_metrics
classmethod
¶
available_metrics
classmethod
¶
aevaluate_sample
abstractmethod
async
¶
Score one sample against every configured metric.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sample
|
EvalSample
|
The interaction to score. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
One |
list[EvaluationResult]
|
class: |
Raises:
| Type | Description |
|---|---|
EvaluationError
|
When a metric cannot be computed. |
Source code in src\windlass\interfaces\evaluator.py
aevaluate
async
¶
Evaluate a dataset and aggregate the results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
samples
|
Sequence[EvalSample | RAGAnswer | dict[str, Any]]
|
Samples, RAG answers, or dicts that coerce into samples. |
required |
Returns:
| Type | Description |
|---|---|
EvaluationReport
|
An aggregated :class: |
Raises:
| Type | Description |
|---|---|
EvaluationError
|
When every sample fails to evaluate. |
Performance
Samples run concurrently up to concurrency. LLM-judged metrics
dominate the cost, so keep an eye on that budget when evaluating
thousands of rows.
Example
import asyncio from windlass.providers.evaluation.builtin import BuiltinEvaluator ev = BuiltinEvaluator(metrics=["answer_relevancy_lexical"]) report = asyncio.run(ev.aevaluate([ ... EvalSample(question="what is rag", answer="rag is retrieval") ... ])) report.samples 1
Source code in src\windlass\interfaces\evaluator.py
evaluate
¶
Guardrail
¶
Guardrail(
*,
on_violation: str = "block",
stages: tuple[str, ...] = ("input", "output"),
name: str | None = None,
**config: Any
)
Bases: Component
Abstract input/output safety check.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
on_violation
|
str
|
One of |
'block'
|
stages
|
tuple[str, ...]
|
Which stages this guardrail runs at — any of |
('input', 'output')
|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Policy-specific options. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
on_violation |
The configured action. |
|
stages |
The stages this guardrail participates in. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
Implementing a guardrail takes one method::
class NoShouting(Guardrail):
provider_name = "no_shouting"
async def acheck(self, content, *, stage="input", context=None):
if content.isupper():
return GuardrailResult(
allowed=False, content=content,
rule="shouting", stage=stage,
)
return GuardrailResult(allowed=True, content=content, stage=stage)
Source code in src\windlass\interfaces\guardrail.py
acheck
abstractmethod
async
¶
acheck(
content: str, *, stage: str = "input", context: dict[str, Any] | None = None
) -> GuardrailResult
Inspect content and return a verdict.
Implementations should report what they found and leave the policy
decision to :attr:on_violation — that keeps one detector usable in
both blocking and redacting configurations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
content
|
str
|
The text to inspect. |
required |
stage
|
str
|
|
'input'
|
context
|
dict[str, Any] | None
|
Extra signals — retrieved chunks, the user id, the tool about to be called. |
None
|
Returns:
| Type | Description |
|---|---|
GuardrailResult
|
The verdict, with |
Source code in src\windlass\interfaces\guardrail.py
avalidate
async
¶
Check content and apply the configured policy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
content
|
str
|
The text to check. |
required |
stage
|
str
|
|
'input'
|
context
|
dict[str, Any] | None
|
Extra signals for the check. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The content to use downstream — unchanged, or redacted. |
Raises:
| Type | Description |
|---|---|
GuardrailViolation
|
When a rule fires and |
Example
import asyncio from windlass.providers.guardrails.rules import RuleGuardrail g = RuleGuardrail(banned_words=["secret"], on_violation="redact") asyncio.run(g.avalidate("the secret plan")) 'the [REDACTED] plan'
Source code in src\windlass\interfaces\guardrail.py
validate
¶
Blocking :meth:avalidate.
check
¶
check(
content: str, *, stage: str = "input", context: dict[str, Any] | None = None
) -> GuardrailResult
Blocking :meth:acheck — returns the verdict without enforcing it.
Source code in src\windlass\interfaces\guardrail.py
GuardrailChain
¶
Bases: Guardrail
Runs several guardrails in order, threading redactions through.
Each guardrail sees whatever the previous one produced, so a PII redactor followed by an injection detector inspects the already-masked text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
guards
|
list[Guardrail] | None
|
The guardrails to run. |
None
|
name
|
str | None
|
Component name for traces. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
guards |
list[Guardrail]
|
The configured guardrails. |
Example
from windlass.providers.guardrails.rules import RuleGuardrail chain = RuleGuardrail(pii=True, on_violation="redact") & RuleGuardrail( ... banned_words=["nope"], on_violation="redact" ... ) chain.validate("a@b.com says nope") '[EMAIL] says [REDACTED]'
Source code in src\windlass\interfaces\guardrail.py
add
¶
acheck
async
¶
acheck(
content: str, *, stage: str = "input", context: dict[str, Any] | None = None
) -> GuardrailResult
Aggregate every child's detections without enforcing them.
Source code in src\windlass\interfaces\guardrail.py
avalidate
async
¶
Run every guardrail's own policy in sequence.
Source code in src\windlass\interfaces\guardrail.py
describe
¶
LLM
¶
LLM(
model: str = "",
*,
temperature: float | None = None,
max_tokens: int | None = None,
timeout: float | None = None,
system_prompt: str | None = None,
name: str | None = None,
**config: Any
)
Bases: Component
Abstract chat-completion model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
str
|
Model identifier passed to the provider. |
''
|
temperature
|
float | None
|
Sampling temperature. Falls back to the global setting. |
None
|
max_tokens
|
int | None
|
Completion ceiling. |
None
|
timeout
|
float | None
|
Per-request timeout in seconds. |
None
|
system_prompt
|
str | None
|
Prepended to every call that does not already start with a system message. |
None
|
name
|
str | None
|
Component name for traces. Defaults to the provider name. |
None
|
**config
|
Any
|
Provider-specific options forwarded verbatim to the SDK. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
model |
The configured model identifier. |
|
temperature |
float
|
The configured sampling temperature. |
max_tokens |
int | None
|
The configured completion ceiling. |
supports_tools |
bool
|
Whether this provider can do native tool calling. |
supports_streaming |
bool
|
Whether this provider can stream. |
Example
Implementing a provider takes one method::
class MyLLM(LLM):
provider_name = "mine"
async def agenerate(self, messages, *, tools=None, **kw):
text = await my_sdk.chat(messages[-1].content)
return Completion(content=text, model=self.model)
Source code in src\windlass\interfaces\llm.py
default_model
classmethod
¶
Return the model used when the caller does not name one.
Returns:
| Type | Description |
|---|---|
str
|
A provider-appropriate default model identifier. |
agenerate
abstractmethod
async
¶
agenerate(
messages: list[Message], *, tools: list[dict[str, Any]] | None = None, **kwargs: Any
) -> Completion
Produce one completion.
This is the only method a provider must implement.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
messages
|
list[Message]
|
The conversation, already normalised and with the system prompt applied. |
required |
tools
|
list[dict[str, Any]] | None
|
JSON-schema tool definitions in OpenAI function-calling format. Adapters translate these into their own shape. |
None
|
**kwargs
|
Any
|
Per-call overrides ( |
{}
|
Returns:
| Type | Description |
|---|---|
Completion
|
The completion, with |
Raises:
| Type | Description |
|---|---|
ProviderError
|
For any provider-side failure. |
Source code in src\windlass\interfaces\llm.py
astream_generate
async
¶
astream_generate(
messages: list[Message], *, tools: list[dict[str, Any]] | None = None, **kwargs: Any
) -> AsyncIterator[StreamEvent]
Produce incremental completion events.
The default implementation calls :meth:agenerate and emits the result
as a single text event followed by done — correct, but not
actually incremental. Providers that support streaming should override
this.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
messages
|
list[Message]
|
The normalised conversation. |
required |
tools
|
list[dict[str, Any]] | None
|
Tool definitions. |
None
|
**kwargs
|
Any
|
Per-call overrides. |
{}
|
Yields:
| Type | Description |
|---|---|
AsyncIterator[StreamEvent]
|
class: |
Source code in src\windlass\interfaces\llm.py
acomplete
async
¶
acomplete(
prompt: PromptLike,
*,
tools: list[dict[str, Any]] | None = None,
retry: bool = True,
**kwargs: Any
) -> Completion
Generate a completion, with retries and timing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
PromptLike
|
A string, a message, or a full transcript. |
required |
tools
|
list[dict[str, Any]] | None
|
Tool definitions the model may call. |
None
|
retry
|
bool
|
Apply the configured retry policy to transient failures. |
True
|
**kwargs
|
Any
|
Per-call overrides forwarded to the provider. |
{}
|
Returns:
| Type | Description |
|---|---|
Completion
|
The completion. |
Raises:
| Type | Description |
|---|---|
ProviderError
|
When the provider fails and retries are exhausted. |
AuthenticationError
|
When credentials are missing or rejected. |
Performance
One network round trip. Set retry=False on latency-critical
paths where you would rather fail fast than wait out a backoff.
Example
import asyncio from windlass.providers.llm.fake import FakeLLM asyncio.run(FakeLLM(responses=["hi"]).acomplete("hello")).content 'hi'
Source code in src\windlass\interfaces\llm.py
complete
¶
complete(
prompt: PromptLike,
*,
tools: list[dict[str, Any]] | None = None,
retry: bool = True,
**kwargs: Any
) -> Completion
Blocking :meth:acomplete.
Safe to call from a notebook or from inside a running event loop; the call is dispatched to a background loop when necessary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
PromptLike
|
A string, a message, or a full transcript. |
required |
tools
|
list[dict[str, Any]] | None
|
Tool definitions the model may call. |
None
|
retry
|
bool
|
Apply the configured retry policy. |
True
|
**kwargs
|
Any
|
Per-call overrides. |
{}
|
Returns:
| Type | Description |
|---|---|
Completion
|
The completion. |
Source code in src\windlass\interfaces\llm.py
astream
async
¶
astream(
prompt: PromptLike, *, tools: list[dict[str, Any]] | None = None, **kwargs: Any
) -> AsyncIterator[StreamEvent]
Stream a completion as it is generated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
PromptLike
|
A string, a message, or a full transcript. |
required |
tools
|
list[dict[str, Any]] | None
|
Tool definitions. |
None
|
**kwargs
|
Any
|
Per-call overrides. |
{}
|
Yields:
| Type | Description |
|---|---|
AsyncIterator[StreamEvent]
|
Stream events. Text deltas arrive as they are produced; the final |
AsyncIterator[StreamEvent]
|
event is always |
Example
import asyncio from windlass.providers.llm.fake import FakeLLM async def main(): ... llm = FakeLLM(responses=["hey"]) ... return "".join([e.delta async for e in llm.astream("hi")]) asyncio.run(main()) 'hey'
Source code in src\windlass\interfaces\llm.py
stream
¶
stream(
prompt: PromptLike, *, tools: list[dict[str, Any]] | None = None, **kwargs: Any
) -> Iterator[StreamEvent]
Blocking :meth:astream.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
PromptLike
|
A string, a message, or a full transcript. |
required |
tools
|
list[dict[str, Any]] | None
|
Tool definitions. |
None
|
**kwargs
|
Any
|
Per-call overrides. |
{}
|
Yields:
| Type | Description |
|---|---|
StreamEvent
|
Stream events, pulled one at a time so nothing is buffered ahead of |
StreamEvent
|
the consumer. |
Source code in src\windlass\interfaces\llm.py
astream_text
async
¶
Stream only the text deltas — the common case for chat UIs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
PromptLike
|
A string, a message, or a full transcript. |
required |
**kwargs
|
Any
|
Per-call overrides. |
{}
|
Yields:
| Type | Description |
|---|---|
AsyncIterator[str]
|
Non-empty text fragments. |
Source code in src\windlass\interfaces\llm.py
abatch
async
¶
abatch(
prompts: Sequence[PromptLike], *, concurrency: int | None = None, **kwargs: Any
) -> list[Completion]
Complete many prompts with bounded concurrency.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompts
|
Sequence[PromptLike]
|
The prompts to run. |
required |
concurrency
|
int | None
|
Maximum simultaneous requests. Defaults to the global
|
None
|
**kwargs
|
Any
|
Per-call overrides applied to every prompt. |
{}
|
Returns:
| Type | Description |
|---|---|
list[Completion]
|
Completions in the same order as |
Performance
Bounded on purpose — an unbounded fan-out over a few hundred prompts is the fastest way to get rate limited.
Example
import asyncio from windlass.providers.llm.fake import FakeLLM llm = FakeLLM(responses=["a", "b"]) [c.content for c in asyncio.run(llm.abatch(["1", "2"]))]['a', 'b']
Source code in src\windlass\interfaces\llm.py
batch
¶
batch(
prompts: Sequence[PromptLike], *, concurrency: int | None = None, **kwargs: Any
) -> list[Completion]
Blocking :meth:abatch.
Loader
¶
Loader(
*,
encoding: str = "utf-8",
metadata: dict[str, Any] | None = None,
recursive: bool = True,
on_error: str = "skip",
name: str | None = None,
**config: Any
)
Bases: Component
Abstract document loader.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
encoding
|
str
|
Text encoding used for character-based formats. |
'utf-8'
|
metadata
|
dict[str, Any] | None
|
Extra metadata merged into every produced document — useful for tagging a whole corpus with a tenant or collection id. |
None
|
recursive
|
bool
|
Whether directory sources are walked recursively. |
True
|
on_error
|
str
|
|
'skip'
|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Loader-specific options. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
extensions |
tuple[str, ...]
|
File suffixes this loader claims, used for auto-detection. |
mimetypes |
tuple[str, ...]
|
MIME types this loader claims. |
Example
Implementing a loader takes one method::
class MyLoader(Loader):
provider_name = "mine"
extensions = (".mine",)
async def aload_source(self, source):
return [Document(content=read(source), source=str(source))]
Source code in src\windlass\interfaces\loader.py
aload_source
abstractmethod
async
¶
Load one concrete source.
Called once per file. Directory expansion and error handling are done
for you by :meth:aload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
SourceLike
|
A single path, URL or byte payload. |
required |
Returns:
| Type | Description |
|---|---|
list[Document]
|
The documents extracted from it. A PDF may return one document per |
list[Document]
|
page or one for the whole file — that is the loader's choice. |
Raises:
| Type | Description |
|---|---|
IngestionError
|
When the source cannot be parsed. |
Source code in src\windlass\interfaces\loader.py
can_handle
classmethod
¶
Return whether this loader claims source.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
SourceLike
|
Path, URL or payload to test. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the extension or scheme matches this loader. |
Example
from windlass.providers.loaders.text import TextLoader TextLoader.can_handle("notes.txt") True TextLoader.can_handle("scan.pdf") False
Source code in src\windlass\interfaces\loader.py
aload
async
¶
aload(
source: SourceLike | Sequence[SourceLike], *, concurrency: int | None = None
) -> list[Document]
Load one or many sources.
Directories are expanded (respecting :attr:recursive and this loader's
:attr:extensions), and files are read concurrently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
SourceLike | Sequence[SourceLike]
|
A path, URL, byte payload, or a sequence of them. A directory path expands to its matching files. |
required |
concurrency
|
int | None
|
Maximum simultaneous reads. Defaults to the global
|
None
|
Returns:
| Type | Description |
|---|---|
list[Document]
|
Every extracted document, with :attr: |
Raises:
| Type | Description |
|---|---|
IngestionError
|
When |
Performance
Blocking parsers run on the thread pool, so a folder of 500 PDFs is parsed in parallel rather than serially.
Source code in src\windlass\interfaces\loader.py
load
¶
astream_load
async
¶
Yield documents one at a time instead of building a list.
Use this for corpora too large to hold in memory: combined with
Pipeline.aingest_stream it keeps peak memory flat regardless of
corpus size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
SourceLike | Sequence[SourceLike]
|
A path, URL, payload, or sequence of them. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[Document]
|
Documents as each source finishes parsing. |
Source code in src\windlass\interfaces\loader.py
MCPClient
¶
MCPClient(
*,
server: str = "",
namespace: bool = False,
timeout: float = 30.0,
name: str | None = None,
**config: Any
)
Bases: Component
Abstract MCP client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
server
|
str
|
Server label used in logs and to namespace tools. |
''
|
namespace
|
bool
|
When True, remote tools are exposed as |
False
|
timeout
|
float
|
Per-call timeout in seconds. |
30.0
|
name
|
str | None
|
Component name. |
None
|
**config
|
Any
|
Transport-specific options (command, args, url, env, ...). |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
connected |
Whether the transport is currently open. |
Example
Implementing a client means three methods::
class MyClient(MCPClient):
provider_name = "mine"
async def aconnect(self): ...
async def alist_tools(self): ...
async def acall_tool(self, name, arguments): ...
Source code in src\windlass\interfaces\mcp.py
aconnect
abstractmethod
async
¶
Open the transport and perform the MCP handshake.
Must be idempotent — calling it twice should not open two sessions.
Raises:
| Type | Description |
|---|---|
MCPError
|
When the server cannot be reached or the handshake fails. |
Source code in src\windlass\interfaces\mcp.py
alist_tools
abstractmethod
async
¶
acall_tool
abstractmethod
async
¶
Invoke a remote tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Tool name as advertised by the server (without any namespace prefix Windlass may have added). |
required |
arguments
|
dict[str, Any]
|
Arguments matching the remote schema. |
required |
Returns:
| Type | Description |
|---|---|
Any
|
The server's result, unwrapped from the MCP content envelope. |
Raises:
| Type | Description |
|---|---|
MCPError
|
When the call fails. |
Source code in src\windlass\interfaces\mcp.py
alist_resources
async
¶
aread_resource
async
¶
Read a resource's contents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
The resource identifier. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The resource body as text. |
Raises:
| Type | Description |
|---|---|
MCPError
|
When the resource cannot be read. |
Source code in src\windlass\interfaces\mcp.py
alist_prompts
async
¶
aget_prompt
async
¶
Instantiate a prompt template.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Prompt name. |
required |
arguments
|
dict[str, Any] | None
|
Template arguments. |
None
|
Returns:
| Type | Description |
|---|---|
str
|
The rendered prompt text. |
Raises:
| Type | Description |
|---|---|
MCPError
|
When the prompt cannot be rendered. |
Source code in src\windlass\interfaces\mcp.py
adisconnect
async
¶
connect
¶
list_tools
¶
call_tool
¶
list_resources
¶
read_resource
¶
list_prompts
¶
get_prompt
¶
disconnect
¶
MCPPrompt
¶
Bases: WindlassModel
A parameterised prompt template advertised by an MCP server.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Prompt identifier. |
description |
str
|
What the prompt is for. |
arguments |
list[dict[str, Any]]
|
Declared arguments, each a dict with |
server |
str
|
Which server advertised it. |
MCPResource
¶
Bases: WindlassModel
A readable resource advertised by an MCP server.
Attributes:
| Name | Type | Description |
|---|---|---|
uri |
str
|
Resource identifier, e.g. |
name |
str
|
Human readable name. |
description |
str
|
What the resource contains. |
mimetype |
str | None
|
Content type, when the server declares one. |
server |
str
|
Which server advertised it. |
MCPToolProxy
¶
MCPToolProxy(
client: MCPClient,
*,
name: str,
description: str = "",
parameters: dict[str, Any] | None = None,
server: str = "",
**config: Any
)
Bases: Tool
A local :class:~windlass.interfaces.tool.Tool backed by a remote MCP tool.
Created by :meth:MCPClient.alist_tools. Calling it forwards to the server
and returns whatever comes back, so agents bind it exactly like a local
function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
MCPClient
|
The MCP client that owns the connection. |
required |
name
|
str
|
Remote tool name. |
required |
description
|
str
|
Remote tool description. |
''
|
parameters
|
dict[str, Any] | None
|
Remote JSON Schema for the arguments. |
None
|
server
|
str
|
Server label, kept in metadata and used to disambiguate same-named tools across servers. |
''
|
**config
|
Any
|
Forwarded to :class: |
{}
|
Source code in src\windlass\interfaces\mcp.py
acall
async
¶
Forward the call to the MCP server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Arguments matching the remote schema. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The server's result. |
Raises:
| Type | Description |
|---|---|
MCPError
|
When the call fails or the server is unreachable. |
Source code in src\windlass\interfaces\mcp.py
Memory
¶
Memory(
*,
max_messages: int | None = None,
return_system: bool = False,
name: str | None = None,
**config: Any
)
Bases: Component
Abstract conversation / long-term memory.
Memory is keyed by thread_id so one instance can serve many concurrent
users — an agent handling a hundred chat sessions needs one memory object,
not a hundred.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_messages
|
int | None
|
Ceiling on how many messages :meth: |
None
|
return_system
|
bool
|
Whether system messages are included in recall. Usually False: the system prompt is supplied by the agent, not the history. |
False
|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Strategy-specific options. |
{}
|
Example
Implementing a memory takes two methods::
class NullMemory(Memory):
provider_name = "null"
async def aadd(self, messages, *, thread_id="default"): ...
async def aget(self, *, thread_id="default", query=None):
return []
Source code in src\windlass\interfaces\memory.py
aadd
abstractmethod
async
¶
Record one or more messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
messages
|
Message | Sequence[Message]
|
A message or a sequence of them. |
required |
thread_id
|
str
|
Conversation this belongs to. |
DEFAULT_THREAD
|
Raises:
| Type | Description |
|---|---|
MemoryError_
|
When the backend cannot persist the messages. |
Source code in src\windlass\interfaces\memory.py
aget
abstractmethod
async
¶
aget(
*,
thread_id: str = DEFAULT_THREAD,
query: str | None = None,
limit: int | None = None
) -> list[Message]
Recall messages for a thread.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
thread_id
|
str
|
Conversation to recall. |
DEFAULT_THREAD
|
query
|
str | None
|
Current user input. Semantic memories use it to rank recall; recency-based memories ignore it. |
None
|
limit
|
int | None
|
Override for :attr: |
None
|
Returns:
| Type | Description |
|---|---|
list[Message]
|
Messages in chronological order, oldest first. |
Source code in src\windlass\interfaces\memory.py
aclear
async
¶
Forget a thread, or everything when thread_id is None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
thread_id
|
str | None
|
Thread to clear. |
None
|
athreads
async
¶
add
¶
get
¶
get(
*,
thread_id: str = DEFAULT_THREAD,
query: str | None = None,
limit: int | None = None
) -> list[Message]
Blocking :meth:aget.
Source code in src\windlass\interfaces\memory.py
clear
¶
Preprocessor
¶
Bases: Component
Abstract document preprocessor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Preprocessor-specific options. |
{}
|
Example
Implementing a preprocessor takes one method::
class Shout(Preprocessor):
provider_name = "shout"
async def aprocess_one(self, document):
return [document.model_copy(
update={"content": document.content.upper()}
)]
Source code in src\windlass\interfaces\base.py
aprocess_one
abstractmethod
async
¶
Transform a single document.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
document
|
Document
|
The document to process. |
required |
Returns:
| Type | Description |
|---|---|
list[Document]
|
Zero, one or many documents. Return |
Raises:
| Type | Description |
|---|---|
IngestionError
|
When the document cannot be processed and dropping it silently would hide a real problem. |
Source code in src\windlass\interfaces\preprocessor.py
aprocess
async
¶
Process a batch of documents concurrently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
documents
|
Sequence[Document]
|
The documents to process. |
required |
concurrency
|
int | None
|
Maximum simultaneous invocations. Defaults to the
global |
None
|
Returns:
| Type | Description |
|---|---|
list[Document]
|
The flattened output, preserving input order. |
Performance
Order is preserved even though execution is concurrent, so a preprocessor that calls an LLM (metadata extraction, contextual enrichment) still yields a deterministic corpus.
Source code in src\windlass\interfaces\preprocessor.py
process
¶
Blocking :meth:aprocess.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
documents
|
Sequence[Document] | Document
|
One document or a sequence of them. |
required |
Returns:
| Type | Description |
|---|---|
list[Document]
|
The processed documents. |
Source code in src\windlass\interfaces\preprocessor.py
PreprocessorChain
¶
Bases: Preprocessor
Runs several preprocessors in sequence.
Each step sees the full output of the previous one, which matters for corpus-level steps like deduplication that need to compare documents against each other rather than in isolation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
steps
|
Sequence[Preprocessor]
|
The preprocessors to run, in order. |
()
|
name
|
str | None
|
Component name for traces. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
steps |
list[Preprocessor]
|
The configured steps. |
Example
from windlass.providers.preprocessors.clean import CleanPreprocessor chain = PreprocessorChain([CleanPreprocessor(min_length=0)]) from windlass.core.types import Document chain.process([Document(content=" hi ")])[0].content 'hi'
Source code in src\windlass\interfaces\preprocessor.py
add
¶
aprocess_one
async
¶
aprocess
async
¶
Run every step in order over the whole batch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
documents
|
Sequence[Document]
|
The documents to process. |
required |
concurrency
|
int | None
|
Forwarded to each step. |
None
|
Returns:
| Type | Description |
|---|---|
list[Document]
|
The output of the final step. Short-circuits to |
list[Document]
|
step drops everything. |
Source code in src\windlass\interfaces\preprocessor.py
describe
¶
Retriever
¶
Retriever(
*,
top_k: int = 5,
score_threshold: float | None = None,
rerank: Any = None,
fetch_k: int | None = None,
name: str | None = None,
**config: Any
)
Bases: Component
Abstract retrieval strategy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
top_k
|
int
|
Default number of chunks to return. |
5
|
score_threshold
|
float | None
|
Drop hits scoring below this. |
None
|
rerank
|
Any
|
Optional reranker applied to the candidate set before
truncation. Any object with an |
None
|
fetch_k
|
int | None
|
How many candidates to pull before reranking/filtering.
Defaults to |
None
|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Strategy-specific options. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
top_k |
The configured result count. |
|
requires_index |
bool
|
Whether :meth: |
Example
Implementing a retriever takes one method::
class RandomRetriever(Retriever):
provider_name = "random"
async def aretrieve_chunks(self, query, k, *, filters=None, **kw):
picks = random.sample(self.corpus, k)
return [ScoredChunk(chunk=c, score=1.0) for c in picks]
Source code in src\windlass\interfaces\retriever.py
aretrieve_chunks
abstractmethod
async
¶
aretrieve_chunks(
query: str, k: int, *, filters: MetadataFilter | None = None, **kwargs: Any
) -> list[ScoredChunk]
Return candidate chunks for query.
The only method a strategy must implement. Thresholding, reranking,
truncation and timing are applied by :meth:aretrieve.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
The search query. |
required |
k
|
int
|
How many candidates to produce. This is |
required |
filters
|
MetadataFilter | None
|
Metadata constraints. |
None
|
**kwargs
|
Any
|
Strategy-specific options. |
{}
|
Returns:
| Type | Description |
|---|---|
list[ScoredChunk]
|
Scored chunks, ideally already sorted by descending score. |
Source code in src\windlass\interfaces\retriever.py
aindex
async
¶
Add chunks to whatever index this retriever maintains.
The default is a no-op, which is right for retrievers that read from a shared vector store. Lexical retrievers override it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chunks
|
Sequence[Chunk]
|
Chunks to index. |
required |
Returns:
| Type | Description |
|---|---|
int
|
How many chunks were indexed. |
Source code in src\windlass\interfaces\retriever.py
index
¶
aretrieve
async
¶
aretrieve(
query: str,
k: int | None = None,
*,
filters: MetadataFilter | None = None,
**kwargs: Any
) -> SearchResult
Retrieve chunks for a query.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
The search query. |
required |
k
|
int | None
|
Override for :attr: |
None
|
filters
|
MetadataFilter | None
|
Metadata constraints. |
None
|
**kwargs
|
Any
|
Strategy-specific options. |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
SearchResult
|
class: |
SearchResult
|
candidate count and latency. |
Raises:
| Type | Description |
|---|---|
RetrievalError
|
When the underlying strategy fails. |
Performance
With a reranker configured, fetch_k candidates are retrieved and
then narrowed to k. Raising fetch_k improves recall at the
cost of one larger rerank call.
Example
import asyncio from windlass.providers.retrievers.bm25 import BM25Retriever from windlass.core.types import Chunk r = BM25Retriever() _ = r.index([Chunk(content="vector search rocks")]) asyncio.run(r.aretrieve("vector")).hits[0].score > 0 True
Source code in src\windlass\interfaces\retriever.py
retrieve
¶
retrieve(
query: str,
k: int | None = None,
*,
filters: MetadataFilter | None = None,
**kwargs: Any
) -> SearchResult
Blocking :meth:aretrieve.
Source code in src\windlass\interfaces\retriever.py
abatch_retrieve
async
¶
abatch_retrieve(
queries: Sequence[str],
k: int | None = None,
*,
filters: MetadataFilter | None = None,
concurrency: int | None = None
) -> list[SearchResult]
Retrieve for many queries concurrently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queries
|
Sequence[str]
|
The queries to run. |
required |
k
|
int | None
|
Override for :attr: |
None
|
filters
|
MetadataFilter | None
|
Metadata constraints applied to every query. |
None
|
concurrency
|
int | None
|
Maximum simultaneous retrievals. |
None
|
Returns:
| Type | Description |
|---|---|
list[SearchResult]
|
One result per query, in input order. |
Source code in src\windlass\interfaces\retriever.py
batch_retrieve
¶
Tool
¶
Tool(
*,
name: str,
description: str = "",
parameters: dict[str, Any] | None = None,
timeout: float | None = None,
requires_approval: bool = False,
**config: Any
)
Bases: Component
Abstract agent-callable capability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Tool name the model sees. Must match |
required |
description
|
str
|
What the tool does. This is prompt text — the model chooses tools based on it, so vague descriptions cause bad calls. |
''
|
parameters
|
dict[str, Any] | None
|
JSON Schema for the arguments. Derived automatically by the decorator; supply it manually when subclassing. |
None
|
timeout
|
float | None
|
Seconds before the call is abandoned. |
None
|
requires_approval
|
bool
|
When True the agent pauses for human approval before invoking it. Use this for anything that spends money or mutates state. |
False
|
**config
|
Any
|
Tool-specific options. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
description |
The model-facing description. |
|
parameters |
dict[str, Any]
|
The JSON Schema for arguments. |
requires_approval |
Whether human-in-the-loop approval is required. |
Example
Implementing a stateful tool::
class SearchTool(Tool):
def __init__(self, client):
super().__init__(
name="search",
description="Search the product catalogue.",
parameters={
"type": "object",
"properties": {"q": {"type": "string"}},
"required": ["q"],
},
)
self.client = client
async def acall(self, **kwargs):
return await self.client.search(kwargs["q"])
Source code in src\windlass\interfaces\tool.py
acall
abstractmethod
async
¶
Execute the tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Arguments matching :attr: |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
Any JSON-serialisable value. It is rendered to a string for the |
Any
|
model and kept intact on :attr: |
Raises:
| Type | Description |
|---|---|
Exception
|
Anything. The agent catches it and reports the failure back to the model rather than crashing the run. |
Source code in src\windlass\interfaces\tool.py
schema
¶
Return the tool definition in a provider's format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
style
|
str
|
|
'openai'
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A dict ready to drop into that provider's request. |
Raises:
| Type | Description |
|---|---|
ValueError
|
For an unknown style. |
Example
from windlass.tools import tool @tool ... def ping() -> str: ... '''Ping.''' ... return "pong" ping.schema(style="anthropic")["name"] 'ping'
Source code in src\windlass\interfaces\tool.py
ainvoke
async
¶
Execute a model-issued tool call and wrap the outcome.
Failures are captured rather than raised: the agent needs to hand the error text back to the model so it can recover, which is impossible if the exception unwinds the loop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
call
|
ToolCall
|
The call requested by the model. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
A |
ToolResult
|
class: |
ToolResult
|
when the tool raised or timed out. |
Performance
Enforces :attr:timeout. A hung tool fails the call, not the run.
Example
import asyncio from windlass.core.types import ToolCall from windlass.tools import tool @tool ... def echo(text: str) -> str: ... '''Echo text.''' ... return text asyncio.run(echo.ainvoke(ToolCall(name="echo", arguments={"text": "hi"}))).content 'hi'
Source code in src\windlass\interfaces\tool.py
invoke
¶
arun
async
¶
Execute the tool directly, outside an agent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**kwargs
|
Any
|
Tool arguments. |
{}
|
Returns:
| Type | Description |
|---|---|
ToolResult
|
The result. |
Raises:
| Type | Description |
|---|---|
ToolExecutionError
|
When the tool fails. Unlike :meth: |
Source code in src\windlass\interfaces\tool.py
run
¶
describe
¶
Return a JSON-safe summary including the schema.
Source code in src\windlass\interfaces\tool.py
NullTracer
¶
Span
¶
Span(
name: str,
*,
kind: str = "chain",
parent_id: str | None = None,
metadata: dict[str, Any] | None = None,
trace_id: str | None = None
)
One timed, attributed unit of work.
Spans are created by :meth:Tracer.span and finished automatically when the
context manager exits. Exceptions are recorded before propagating, so a
failed run is still fully traced.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Human readable operation name. |
required |
kind
|
str
|
One of :data: |
'chain'
|
parent_id
|
str | None
|
Enclosing span's id, when nested. |
None
|
metadata
|
dict[str, Any] | None
|
Static attributes attached at creation. |
None
|
trace_id
|
str | None
|
Groups spans belonging to one top-level request. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
id |
Unique span id. |
|
started_at |
Monotonic start time. |
|
ended_at |
float | None
|
Monotonic end time, set on :meth: |
error |
str | None
|
Error message, when the span failed. |
usage |
Usage | None
|
Token accounting, for model spans. |
Source code in src\windlass\interfaces\tracer.py
set_input
¶
set_output
¶
set_usage
¶
set_metadata
¶
set_error
¶
end
¶
attach_native
¶
native
¶
to_dict
¶
Return a JSON-safe representation of the span.
Source code in src\windlass\interfaces\tracer.py
Tracer
¶
Tracer(
*,
enabled: bool = True,
project: str | None = None,
tags: tuple[str, ...] = (),
name: str | None = None,
**config: Any
)
Bases: Component
Abstract tracing backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled
|
bool
|
Master switch. Disabled tracers still create spans (so code paths are identical) but never export them. |
True
|
project
|
str | None
|
Project / session name shown in the backend's UI. |
None
|
tags
|
tuple[str, ...]
|
Tags applied to every span. |
()
|
name
|
str | None
|
Component name. |
None
|
**config
|
Any
|
Backend-specific options. |
{}
|
Example
Implementing a tracer takes one method::
class PrintTracer(Tracer):
provider_name = "print"
def start_span(self, span): ...
def end_span(self, span):
print(span.to_dict())
Source code in src\windlass\interfaces\tracer.py
start_span
abstractmethod
¶
Called when a span begins.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
span
|
Span
|
The span that just started. Attach a backend object with
:meth: |
required |
end_span
¶
Called when a span ends, successfully or not.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
span
|
Span
|
The finished span. |
required |
flush
¶
Force any buffered spans to be exported.
Backends that batch should override this; a process that exits without flushing loses its last traces.
span
¶
Open a span around a block of work.
Spans nest automatically: a span opened inside another becomes its child, which is what produces a readable trace tree from plain code.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Operation name. |
required |
kind
|
str
|
One of :data: |
'chain'
|
inputs
|
Any
|
The operation's input, recorded immediately. |
None
|
**metadata
|
Any
|
Extra attributes. |
{}
|
Yields:
| Type | Description |
|---|---|
Span
|
The live :class: |
Raises:
| Type | Description |
|---|---|
Exception
|
Anything the block raises, after recording it. |
Example
from windlass.providers.observability.console import ConsoleTracer with ConsoleTracer(enabled=False).span("work", kind="tool") as s: ... s.set_output(42)
Source code in src\windlass\interfaces\tracer.py
current_span
¶
event
¶
Record a zero-duration event on the current span.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Event name. |
required |
**fields
|
Any
|
Event attributes. |
{}
|
Source code in src\windlass\interfaces\tracer.py
VectorStore
¶
VectorStore(
*,
collection: str = "windlass",
dimensions: int | None = None,
metric: str = "cosine",
persist_path: str | None = None,
name: str | None = None,
**config: Any
)
Bases: Component
Abstract vector database.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
collection
|
str
|
Name of the collection / index / namespace to use. |
'windlass'
|
dimensions
|
int | None
|
Vector dimensionality. Required by stores that must create an index up front; inferred on first write by those that can. |
None
|
metric
|
str
|
Similarity metric — |
'cosine'
|
persist_path
|
str | None
|
Where an on-disk store should keep its data. |
None
|
name
|
str | None
|
Component name for traces. |
None
|
**config
|
Any
|
Store-specific options. |
{}
|
Attributes:
| Name | Type | Description |
|---|---|---|
collection |
The active collection name. |
|
metric |
The configured similarity metric. |
|
supports_filters |
bool
|
Whether metadata filtering is pushed down to the store rather than applied client-side. |
supports_hybrid |
bool
|
Whether the store has native sparse+dense search. |
Example
Implementing a store means four methods::
class MyStore(VectorStore):
provider_name = "mine"
async def aadd(self, chunks): ...
async def asearch(self, vector, k=5, filters=None): ...
async def adelete(self, ids=None, filters=None): ...
async def acount(self): ...
Source code in src\windlass\interfaces\vectordb.py
aadd
abstractmethod
async
¶
Insert or update chunks.
Chunk ids are deterministic, so re-adding the same content must upsert rather than duplicate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chunks
|
Sequence[Chunk]
|
Chunks with :attr: |
required |
Returns:
| Type | Description |
|---|---|
int
|
How many chunks were written. |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If any chunk is missing its embedding. |
ProviderError
|
For store-side failures. |
Source code in src\windlass\interfaces\vectordb.py
asearch
abstractmethod
async
¶
asearch(
vector: Sequence[float],
k: int = 5,
*,
filters: MetadataFilter | None = None,
**kwargs: Any
) -> list[ScoredChunk]
Find the k nearest chunks to vector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vector
|
Sequence[float]
|
The query embedding. |
required |
k
|
int
|
How many results to return. |
5
|
filters
|
MetadataFilter | None
|
Metadata constraints. Stores without native filtering
should call :meth: |
None
|
**kwargs
|
Any
|
Store-specific search options. |
{}
|
Returns:
| Type | Description |
|---|---|
list[ScoredChunk]
|
Hits sorted by descending score, each with |
Source code in src\windlass\interfaces\vectordb.py
adelete
abstractmethod
async
¶
Delete chunks by id or by metadata filter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
Sequence[str] | None
|
Chunk ids to remove. |
None
|
filters
|
MetadataFilter | None
|
Metadata constraints selecting what to remove. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
How many chunks were deleted. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If neither |
Source code in src\windlass\interfaces\vectordb.py
acount
abstractmethod
async
¶
aget
async
¶
Fetch chunks by id.
The default returns []; stores that can look up by id should
override it. Parent-child retrieval relies on this to expand a matched
child into its parent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
Sequence[str]
|
Chunk ids to fetch. |
required |
Returns:
| Type | Description |
|---|---|
list[Chunk]
|
The chunks that exist, in whatever order the store returns them. |
Source code in src\windlass\interfaces\vectordb.py
aclear
async
¶
Remove every chunk from the collection.
The default deletes by listing ids, which is correct but slow; stores with a native truncate should override it.
Source code in src\windlass\interfaces\vectordb.py
apersist
async
¶
add
¶
search
¶
search(
vector: Sequence[float],
k: int = 5,
*,
filters: MetadataFilter | None = None,
**kwargs: Any
) -> list[ScoredChunk]
Blocking :meth:asearch.
Source code in src\windlass\interfaces\vectordb.py
delete
¶
count
¶
get
¶
clear
¶
persist
¶
validate_embeddings
staticmethod
¶
Assert that every chunk carries an embedding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chunks
|
Sequence[Chunk]
|
Chunks about to be written. |
required |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
Naming the first chunk that is missing one. |
Source code in src\windlass\interfaces\vectordb.py
match_filters
staticmethod
¶
Evaluate a metadata filter client-side.
Supports plain equality plus the Mongo-style operators $eq, $ne,
$gt, $gte, $lt, $lte, $in, $nin, $contains
and $exists. Stores without native filtering use this so that filter
semantics are identical across every backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metadata
|
dict[str, Any]
|
The chunk's metadata. |
required |
filters
|
MetadataFilter | None
|
The constraints. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True when the metadata satisfies every constraint. |
Example
VectorStore.match_filters({"year": 2024}, {"year": {"$gte": 2020}}) True VectorStore.match_filters({"tag": "a"}, {"tag": {"$in": ["b"]}}) False
Source code in src\windlass\interfaces\vectordb.py
rank
staticmethod
¶
Sort hits by descending score and assign 1-based ranks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hits
|
list[ScoredChunk]
|
Unordered hits. |
required |
Returns:
| Type | Description |
|---|---|
list[ScoredChunk]
|
The same objects, sorted and with |