Skip to main content
A
Docs

Shared Library

The anar_shared Python package providing LLM abstraction, auth, production hardening, and telemetry.

Overview

anar_shared is the shared Python package used by all 13 FastAPI backends. It provides a unified interface for LLM providers, JWT authentication, production middleware, service-to-service communication, OpenTelemetry setup, and Guard telemetry. Version: 0.3.0.

Installation

From any product backend directory:

uv pip install -e ../../../shared/python

For development with test dependencies:

cd shared/python
uv pip install -e ".[dev]"

Module Reference

ModuleExports
anar_shared.authmake_auth, make_require_role, TokenData
anar_shared.llmLLMConfig, LLMProvider, GroqLLM, AzureLLM, Core42LLM, GatewayLLM, get_llm, get_llm_for_model, get_openai_client, resolve_provider, MODEL_PROVIDER_MAP
anar_shared.productionharden, setup_logging, health_response, record_start_time, register_error_handlers, JSONFormatter, RequestIDMiddleware, TimingMiddleware, SecurityHeadersMiddleware, RequestSizeLimitMiddleware, RateLimiter, RateLimitMiddleware
anar_shared.servicesAnarServiceClient, ServiceConfig
anar_shared.otelsetup_otel
anar_shared.telemetryGuardTelemetry, llm_call_timer

Production Hardening

The harden() function applies the entire middleware stack and error handlers to a FastAPI app in a single call:

from fastapi import FastAPI
from anar_shared import harden, health_response

app = FastAPI()

harden(
    app,
    product="anar-chat",
    debug=False,
    heavy_paths=["/api/v1/chat/completions"],
    max_body_bytes=10 * 1024 * 1024,
)

@app.get("/health")
async def health():
    return health_response("anar-chat", version="0.1.0")

Middleware Stack (applied in order)

MiddlewareFunction
RateLimitMiddlewareIn-memory rate limiting (100 RPM default, 10 RPM for heavy paths)
RequestSizeLimitMiddlewareRejects requests exceeding max_body_bytes with 413
RequestIDMiddlewareGenerates/propagates X-Request-ID header
TimingMiddlewareAdds X-Response-Time header and logs access
SecurityHeadersMiddlewareAdds X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security

Error Handlers

register_error_handlers (called by harden) registers two handlers:

  • Unhandled exceptions return 500 with {"error": "Internal server error"}
  • Validation errors return 422 with {"error": "Validation error", "details": [...]}

LLM Abstraction

The LLMProvider abstract class defines a consistent interface across all providers:

from anar_shared import LLMConfig, get_llm, get_llm_for_model

config = LLMConfig(
    provider="groq",
    groq_api_key="gsk_...",
    default_model="llama-3.3-70b-versatile",
)

llm = get_llm(config)
response = await llm.chat([
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What is the capital of Oman?"},
])

Provider Classes

ClassProviderKey Config Fields
GroqLLMGroqgroq_api_key, groq_base_url
AzureLLMAzure OpenAIazure_api_key, azure_endpoint, azure_deployment
Core42LLMSovereign cloudcore42_api_key, core42_base_url
GatewayLLMAnar Gatewaygateway_url, gateway_api_key

LLMProvider Interface

Every provider implements three methods:

async def chat(messages, model=None, *, temperature=0.7, max_tokens=2048) -> str
async def chat_stream(messages, model=None, *, temperature=0.7, max_tokens=2048) -> AsyncIterator[str]
async def vision(prompt, image_b64, *, model=None, detail="high", max_tokens=4096) -> str

Automatic Provider Resolution

get_llm_for_model selects the correct provider based on the model name and automatically routes through the Gateway when configured:

llm = get_llm_for_model("allam-2-7b", config)
# If config.gateway_url is set → GatewayLLM
# Otherwise → GroqLLM (allam-2-7b maps to groq)

Streaming

Use chat_stream for token-by-token output:

async for token in llm.chat_stream([
    {"role": "user", "content": "Explain quantum computing"}
]):
    print(token, end="", flush=True)

Service Client

AnarServiceClient provides typed methods for calling other Anar products:

from anar_shared import AnarServiceClient, ServiceConfig

client = AnarServiceClient(ServiceConfig(
    guard_url="http://guard-backend:8002",
    docs_url="http://docs-backend:8008",
    auth_token="your-service-token",
))

scan = await client.guard_scan("Check this content for PII")
print(scan.safe, scan.risk_level, scan.categories)

pii = await client.guard_pii_detect("My ID is 784-1234-1234567-1")

doc = await client.docs_process(file_bytes, "contract.pdf")
print(doc.text, doc.confidence, doc.language)

Available Service Methods

MethodServiceDescription
guard_scan(text, policies)GuardContent safety scanning
guard_pii_detect(text)GuardPII detection
docs_process(file_bytes, filename)DocsDocument processing and OCR
docs_ocr(file_bytes, filename)DocsRaw OCR extraction
chat_rag_query(query, collection)ChatRAG knowledge base query
chat_rag_ingest(chunks, collection)ChatIngest chunks into RAG
chat_ingest_chunks(kb_id, doc_id, filename, chunks)ChatStructured RAG ingest
voice_transcribe(audio_bytes, filename, language)VoiceAudio transcription
present_generate(prompt, template_id)PresentSlide generation
comply_assess(content, framework)ComplyCompliance assessment
flow_trigger(workflow_id, inputs)FlowTrigger a workflow
translate(text, source_lang, target_lang, domain)TranslateDocument translation
minutes_summarize(meeting_id)MinutesMeeting summarization
procure_analyze_tender(tender_id)ProcureTender analysis
health_check(service)AnyService health check

Structured Logging

setup_logging configures JSON-formatted log output suitable for log aggregation:

from anar_shared import setup_logging

setup_logging(debug=False)

Log output format:

{
  "timestamp": "2026-02-16T10:30:45.123456+00:00",
  "level": "INFO",
  "module": "production",
  "message": "GET /api/v1/health 200 12.45ms",
  "request_id": "550e8400-e29b-41d4-a716-446655440000"
}

LLM Call Timer

The llm_call_timer utility measures LLM call latency for telemetry:

from anar_shared import llm_call_timer

with llm_call_timer() as timer:
    response = await llm.chat(messages)

telemetry.record(
    model="llama-3.3-70b-versatile",
    latency_ms=timer.elapsed_ms,
)