castia
Idiomatic, FastAPI-style Python SDK for Microsoft Foundry hosted agents.
castia lets a hosted agent speak Foundry's three wire protocols — Activity
(Teams/Bot Framework), OpenAI responses, and invocations — through
protocol-named decorators, dependency injection (Depends), and typed builders
for messages, Adaptive Cards, entities, and invoke envelopes. You decorate a
handler, return a value, and the framework does the rest — the auth chains,
hosting, Activity routing, and telemetry stay out of your file.
Install
pip install castia
# or, with uv:
uv add castia
Requires Python 3.11+.
Quickstart
from castia import Agent, Depends, Model, Teams
app = Agent(name="my-agent")
def gpt4o() -> Model:
return Model("gpt-4o")
@app.message(Teams.direct, Teams.group, Teams.channel_mention)
async def reply(text: str, model: Model = Depends(gpt4o)) -> str:
return await model.respond(text)
if __name__ == "__main__":
app.run()
Decorate a handler with the surfaces it answers on, return a str, and the
framework sends it as the Teams reply. The model is built once for the
process and injected via Depends — the model choice stays visible in your file
instead of being buried in the framework. (Model() with no argument falls back
to AZURE_AI_MODEL_DEPLOYMENT_NAME; get_model / use_model("gpt-4o") are
zero-config conveniences.)
Composing protocols with routers
Like FastAPI's include_router, an Agent composes Routers so each protocol
can live in its own module:
from castia import Agent
from handlers import activity, responses, invocations
app = Agent(name="my-agent")
app.include(activity.router, responses.router, invocations.router)
Richer replies
Handlers can take a Message and reach for typed builders — Adaptive Cards,
suggested actions, citations, mentions, sensitivity labels, live-typing
streamers, and reactions:
from castia import Depends, Message, Model, Reaction, Router, Teams
router = Router()
@router.activity(Teams.direct)
async def reply(text: str, msg: Message, model: Model = Depends(get_model)) -> None:
await msg.react(Reaction.eyes)
answer = await model.respond(text)
await msg.say(answer)
Protocols
castia publishes handlers for the protocols in PUBLISHABLE_PROTOCOLS:
- Activity — Teams / Bot Framework message and invoke turns.
responses— the OpenAIresponseswire shape.invocations— Foundry invoke envelopes (tool execution, agent-to-agent).
Observability & evaluation
castia configures Foundry/Agent 365 telemetry for you when the agent starts.
By default it emits GenAI spans (the chat {model} spans the Foundry Traces UI
keys off) but does not record the prompt/response content onto them.
Recording content is what makes an agent's traces evaluable — trace-based
evaluators read the input/output text from the GenAI spans, which is only present
when content recording is enabled. Turn it on deliberately via
configure_observability:
from castia.observability import configure_observability
# Records prompt/response text onto GenAI spans so traces can be evaluated.
configure_observability(enable_content_recording=True)
Resolution order for each flag is explicit argument > environment variable > default:
| Flag | Argument | Environment variable | Default |
|---|---|---|---|
| Content recording | enable_content_recording |
AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED |
off |
| GenAI tracing | enable_genai_tracing |
AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING |
on |
Passing nothing preserves the default behavior. Telemetry setup is best-effort: a failure is logged, never raised, so it can't break startup or a turn.
Security caveat: enabling content recording writes prompt and response text to Application Insights. Only enable it where storing that content is acceptable for your data-handling and privacy requirements.
Building a scored eval suite
Once your traces are evaluable, python -m castia eval wraps the
azd ai agent eval extension to synthesize and run a scored eval suite — a
generated JSONL dataset plus an auto-generated, weighted rubric (a custom
multi-dimension evaluator):
# Offline gate — validate eval.yaml + rubric files, no Azure, free in CI:
python -m castia eval check
# Synthesize a rubric + dataset from the agent instruction (billable):
python -m castia eval generate --agent my-agent --max-samples 25
# Re-upload locally edited rubric/dataset files as a new version:
python -m castia eval update --evaluator-only
# Submit a scored run against the deployed agent (billable):
python -m castia eval run
check is a pure, offline referential-integrity gate: it resolves every
evaluator/dataset local_uri and validates each rubric dimensions file.
generate and run submit billable Foundry jobs, so both accept
--dry-run to print the resolved azd command line without submitting
anything. The azd wrappers need the build-time extra: pip install 'castia[deploy]'.
The rubric dimensions file is a bare JSON list where each entry is keyed by
id (a stable slug like correct_outcome), with an optional
always_applicable: true on the catch-all dimension. That cross-SDK shape is
pinned in the monorepo at spec/conformance/rubric/.
Design
castia is deliberately import-cheap: import castia never pulls in the
instrumented Azure/OpenAI/httpx stacks, so telemetry can be configured before
those libraries load. The heavy imports are deferred into the methods that need
them.
License
MIT © 2026 Seth Juarez
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file castia-0.1.0.tar.gz.
File metadata
- Download URL: castia-0.1.0.tar.gz
- Upload date:
- Size: 75.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a297c0cd49219418dd780589bb1d4eafd9b5ee02e146e6f8da2e6aa9fa54a431
|
|
| MD5 |
9ecae4e18d955638cadf506ca283d276
|
|
| BLAKE2b-256 |
f13d8fc0abedfadc2ea2fe927d588f0f499d9628c6a2c2c9773e5fcbe5f0bcf5
|
Provenance
The following attestation bundles were made for castia-0.1.0.tar.gz:
Publisher:
release-please.yml on sethjuarez/castia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
castia-0.1.0.tar.gz -
Subject digest:
a297c0cd49219418dd780589bb1d4eafd9b5ee02e146e6f8da2e6aa9fa54a431 - Sigstore transparency entry: 2771601729
- Sigstore integration time:
-
Permalink:
sethjuarez/castia@c3b9ce29377d6bc872fd1dfac0ef6d4ad66b7d71 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sethjuarez
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@c3b9ce29377d6bc872fd1dfac0ef6d4ad66b7d71 -
Trigger Event:
push
-
Statement type:
File details
Details for the file castia-0.1.0-py3-none-any.whl.
File metadata
- Download URL: castia-0.1.0-py3-none-any.whl
- Upload date:
- Size: 77.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c5c2b87e3b802b3f3115026f15c9c554d574b9854f6aca66c9d4e7cbc1948e89
|
|
| MD5 |
c88a6c4a827b9bbd19b775f41496fe01
|
|
| BLAKE2b-256 |
e164ac4ce152c1815fe828995784e7b62be85864ffe18009eb9125daa09b0156
|
Provenance
The following attestation bundles were made for castia-0.1.0-py3-none-any.whl:
Publisher:
release-please.yml on sethjuarez/castia
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
castia-0.1.0-py3-none-any.whl -
Subject digest:
c5c2b87e3b802b3f3115026f15c9c554d574b9854f6aca66c9d4e7cbc1948e89 - Sigstore transparency entry: 2771601831
- Sigstore integration time:
-
Permalink:
sethjuarez/castia@c3b9ce29377d6bc872fd1dfac0ef6d4ad66b7d71 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/sethjuarez
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-please.yml@c3b9ce29377d6bc872fd1dfac0ef6d4ad66b7d71 -
Trigger Event:
push
-
Statement type: