Integrating langshark-bites¶
One reference for wiring the bites into an existing LangChain / LangGraph project — written to be read by an AI coding agent before it edits anything.
For AI coding agents — read this first¶
- Read this page, then the bite's
docs/<bite>.mdpage and itsexamples/<bite>.py, before writing code. - Use only the public API re-exported in
src/langshark_bites/__init__.py(and each bite's own__init__.py). Do not reach into private modules or attributes. - Ignore the authoring internals:
skills/langshark-bites/SKILL.mdandtemplates/bite/are for editing the library itself, not for consuming it.main.pyat the repo root is a hello-world remnant, never an entrypoint. - Do not add dependencies. The base package covers every bite except
a2a_completion_notifier, which needs the[a2a-notifier]extra. - Preserve the project's existing retry / fallback / error-handling semantics unless the task explicitly changes them.
- Verify with the single gate at the bottom of this page.
Which install you need¶
| Bite | Install |
|---|---|
api_rate_limiter, api_backoff, provider_failover, json_output_parser, state_reducers, observability |
uv add langshark-bites |
a2a_completion_notifier |
uv add "langshark-bites[a2a-notifier]" |
The a2a-notifier extra is required even for just the emitter: the
package imports the FastAPI receiver at module load.
Decision table¶
| Bite | Where it plugs into your code | Verify (learn from the example) |
|---|---|---|
api_rate_limiter |
wrap the external API call function | examples/rate_limiter.py |
api_backoff |
replace asyncio.sleep in retry loops |
examples/backoff.py |
provider_failover |
where the LLM model is constructed | examples/provider_failover.py |
json_output_parser |
where the model's free-text JSON arrives | examples/json_output_parser.py |
state_reducers |
the LangGraph state annotation | examples/state_reducers.py |
observability |
startup hook + the hot functions | examples/observability.py |
a2a_completion_notifier |
subagent graph factory (emitter) + FastAPI receiver next to the supervisor | examples/a2a_completion_notifier.py |
api_rate_limiter¶
Share one token-bucket budget across every process and replica. The bucket lives in Redis and is updated atomically with a Lua script; if Redis is unreachable it falls back to an in-process bucket so the system stays up.
from langshark_bites.api_rate_limiter import RateLimiter, rate_limited
limiter = RateLimiter.from_env()
@rate_limited(limiter, provider="newsapi")
async def fetch_news(ticker: str): ...
- Config:
RateLimiter.from_env()readsREDIS_URL(defaultredis://localhost:6379/0) and provider config fromRATE_LIMIT_CONFIG_PATH(YAML) or per-provider env vars. There are no baked-in providers — you define the ones your project needs. - Prerequisite: a Redis instance for a shared budget (optional in dev — the in-process fallback still limits per process).
api_backoff¶
A drop-in replacement for asyncio.sleep in retry loops that makes the wait
visible in the logs.
from langshark_bites.api_backoff import async_backoff
async def fetch_news_with_retry(ticker: str) -> str:
for attempt in range(1, 4):
try:
return await fetch_news(ticker)
except ThrottledError:
await async_backoff(2**attempt, context=f"newsapi retry {attempt}/3")
- Config: none — just the
contextstring that lands in the log event.
provider_failover¶
Build an LLM with a fallback chain and a process-level circuit breaker, so an exhausted provider is skipped instead of wasting round-trips.
from langshark_bites.provider_failover import model_with_fallbacks
model = model_with_fallbacks(
"claude-sonnet-4-5",
"deepseek-v4-flash,gpt-4o-mini",
max_tokens=8192,
model_builder=create_model, # your (model_name, max_tokens) -> BaseChatModel
)
- Config: provider names, the comma-separated fallback list, and your
model_builder. - Note: rate-limit (429) errors are excluded by design — they belong
to retry middleware, not the fallback chain.
ExhaustedProviderCallbackattaches to a model instance to detect credit/billing errors.
json_output_parser¶
Extract and validate free-text JSON from models that reject
response_format (e.g. DeepSeek thinking mode).
from langshark_bites.json_output_parser import extract_structured_from_messages
content = extract_structured_from_messages(state.get("messages", []), Analysis)
- Config: the pydantic model to validate against.
state_reducers¶
Merge parallel subagent results into graph state without duplicates, by upserting on a key.
from typing import Annotated
from langshark_bites.state_reducers import envelope_reducer
class State(TypedDict):
collected_outputs: Annotated[list[dict], envelope_reducer]
- Config: none — a state annotation change only.
observability¶
OpenInference / Phoenix tracing with span decorators, without dragging the
full arize-phoenix SDK into the agent process.
from langshark_bites.observability import init_phoenix, agent_span
init_phoenix(endpoint="http://localhost:6006", project_name="my-app")
@agent_span(parse_agent_name=True, tags={"as_of": "2026-07-10"})
async def run_worker(agent_name: str, as_of: str, ...):
...
- Config:
init_phoenix(endpoint=..., project_name=...)once per process (under ASGI, call it viaasyncio.to_thread/ at startup — registration scans the SDK and can take seconds). It is idempotent and soft-fails if the collector is unreachable. - Prerequisite: a Phoenix collector running as its own service (e.g. the official Docker container) — never inside the agent process.
a2a_completion_notifier¶
Push completion notifications between split Agent Server deployments: an emitter middleware on the subagent graph and a FastAPI receiver next to the supervisor.
# Emitter (subagent side — in a dynamic graph factory)
from langshark_bites.a2a_completion_notifier.middleware import (
build_a2a_notifier_from_config,
)
from langshark_bites.a2a_completion_notifier.push_client import PushClient
from langshark_bites.a2a_completion_notifier.signer import A2ASigner
def make_graph(config):
notifier = build_a2a_notifier_from_config(
config,
signer=A2ASigner(pem, kid="subagent-1", issuer=..., audience=...),
push_client=PushClient(),
)
return create_agent(model=..., tools=..., middleware=[notifier])
# Receiver (supervisor side): serve the app with uvicorn, next to the supervisor.
import uvicorn
from langshark_bites.a2a_completion_notifier.receiver import create_receiver_app
from langshark_bites.a2a_completion_notifier.settings import ReceiverSettings
app = create_receiver_app(settings=ReceiverSettings.from_env())
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=ReceiverSettings.from_env().receiver_port)
- Config:
A2A_*env vars —A2A_SUPERVISOR_URL,A2A_SUPERVISOR_API_KEY,A2A_CALLBACK_TOKEN_SECRET, receiver URL/host/port (defaultshttp://localhost:8001/0.0.0.0/8001), the subagent JWKS URL, and the signer's RSA private key +kid/issuer/audience. An optionalA2A_REDIS_URLenables cross-replicajtidedup. Delivery failures surface asa2a_forward_failed/a2a_forward_wake_failedlog events. - Prerequisite: an RSA/JWKS key pair and a callback-token secret. Redis
is optional — it enables cross-replica dedup (
RedisJtiStore); the default is in-memory. - Install:
uv add "langshark-bites[a2a-notifier]".
Single verification gate¶
For each bite you integrate:
- Learn from the bite's example — read
examples/<bite>.py(in this repo or the docs site) to see the API in action. Every example runs without external services (Redis, Phoenix, live APIs, network). - Wire it into the project at the named integration point.
- Verify with the project's own gate — the single test/lint command the consuming repo already uses.
An integration is done when that gate is green and the diff touches only the intended call sites.
Keeping this page honest¶
This page is instruction content for autonomous agents — it must stay in sync
with the bites' actual APIs. It is part of the docs build
(uv run mkdocs build --strict fails on broken links), so treat an edit to a
bite as an edit to this page too.