-
Notifications
You must be signed in to change notification settings - Fork 2
SDK Assistant Provider
Ask Cairn talks to an LLM through a deliberately small interface, so the backend is a runtime choice rather than a dependency of the engine. Four providers ship : Mistral (the default), OpenAI and any OpenAI-compatible endpoint, Anthropic Claude, and self-hosted Ollama.
assistant/providers/base.py asks a backend for three operations, and nothing
else:
class BaseClient:
def chat_json(self, messages, json_schema, think=None):
"""Return the parsed object the model produced under the schema constraint."""
def chat_text(self, messages):
"""Return a plain string."""
def embed(self, texts):
"""Return one embedding vector (list[float]) per input string."""chat_json drives tool routing : the engine asks the model which read-only
tools to call, constrained by a JSON Schema. chat_text produces the final
summary sentence. embed backs semantic requirement search and is optional : a
provider without embeddings (Claude, today) simply raises, and semantic search
is then configured against another provider or left off.
The engine depends on this surface alone, which is why adding a backend touches no engine code.
Raise the right one. They are what the API turns into a stable 503 code, and
what tells an operator whether the problem is theirs or the provider's.
| Exception | Means |
|---|---|
ServiceUnreachable |
Cannot be reached, or returned a server error |
ModelNotAvailable |
The configured model is unknown to the backend |
MalformedModelOutput |
The response does not parse as expected |
AssistantDisabled |
The feature flag is off |
Swallowing a failure and returning an empty answer is the one thing not to do : the assistant labels its summaries as AI-generated and cites real records, and a silent degradation breaks that contract without telling anyone.
assistant/providers/<name>.py:
class MyProviderClient(BaseClient):
def __init__(self):
self.base_url = settings.AI_ASSISTANT_BASE_URL or "https://api.example.com/v1"
self.model = settings.AI_ASSISTANT_MODEL
self.api_key = settings.AI_ASSISTANT_API_KEY
def chat_json(self, messages, json_schema, think=None):
try:
response = httpx.post(
f"{self.base_url}/chat/completions",
json={"model": self.model, "messages": messages,
"response_format": {"type": "json_schema",
"json_schema": json_schema}},
headers={"Authorization": f"Bearer {self.api_key}"},
timeout=httpx.Timeout(settings.AI_ASSISTANT_TIMEOUT,
connect=settings.AI_ASSISTANT_CONNECT_TIMEOUT),
)
except httpx.RequestError as exc:
raise ServiceUnreachable(str(exc)) from exc
...Use httpx, which is already a dependency, and honour both timeouts. They are
separate on purpose : a provider that is down should fail in two seconds, while
one that is merely slow gets thirty. Ignoring them turns a provider outage into
a hung command palette.
Then add the branch to get_client() in base.py. Selection is by
AI_ASSISTANT_PROVIDER, lower-cased, and accepting an alias is fine
(anthropic and claude both resolve to the same client).
Update .env.example and the
assistant specification with the provider's
configuration and, importantly, its data-egress properties. Enabling the
assistant sends the question text and the compact record fields used for routing
to whoever runs the endpoint. An operator choosing a provider is making a data
protection decision, and the documentation is where they make it.
Regenerate the reference so any new setting appears:
python manage.py generate_docsassistant/tests/. Mock the HTTP layer; do not call a live provider in the
suite. Cover: a well-formed response parses; a connection error raises
ServiceUnreachable; an unknown model raises ModelNotAvailable; malformed
content raises MalformedModelOutput; and get_client() returns your client
for the provider string and its aliases.
- Client implements
chat_json,chat_textand, if supported,embed - Both timeouts honoured
- The four failure modes raise the right exception
- Branch added to
get_client() -
.env.exampleand the assistant specification updated, egress described -
generate_docsre-run and committed - Tests mock the transport and cover every failure mode
Built from docs/ at v0.36.0. Edits made here are overwritten by the next release : open a pull request against the source instead.
- Administration
- Ask Cairn
- Assets and suppliers
- Compliance
- The dashboard
- Finding your way
- Getting started
- Incidents
- How records move
- Organisational context
- Reports and management review
- Risks
- Trust Center
- Architecture
- Configuration
- Contributing
- The documentation system
- Installation
- Internationalisation
- Operations
- Release process
- Security
- Testing
- Adding an assistant provider
- Adding a dashboard widget
- Adding a domain entity
- Declaring a lifecycle
- Adding an MCP tool
- Adding a REST endpoint
- Adding a report
- Interface conventions
- Dashboard widgets
- Lifecycles
- MCP tools
- MCP tool parameters : Assets
- MCP tool parameters : Compliance
- MCP tool parameters : Governance and context
- MCP tool parameters : General
- MCP tool parameters : Incidents
- MCP tool parameters : Reports and management review
- MCP tool parameters : Risks
- MCP tool parameters : System and administration
- MCP tool parameters : Trust Center
- Management commands
- Models
- Permissions
- REST endpoints
- Environment variables
- MCP server
- REST API
- Assistant module (Ask Cairn)
- Module 0: User Management and Access Control
- Module 1: Context and Organization
- Module 2: Asset Management
- Module 3: Compliance
- Module 4: Risk Management
- Module 4 bis - EBIOS Risk Manager
- Module 5 : Trust Center
- Module 6 : Security Incident Management
- Management review : ISO 27001:2022 compliance (clause 9.3)