Python SDK
Der generierte Client aacsearch — Installation, Konstruktor, Keyword-only-Methoden, AACSearchError und der Retry-Vorbehalt.
Der Python-Client liegt in packages/sdk-python, Distributionsname aacsearch, Paket aacsearch. Er benötigt Python 3.9+ und requests>=2.31 und wird aus demselben OpenAPI-Dokument erzeugt wie der TypeScript-Client.
pip install -e packages/sdk-python # innerhalb des Monoreposfrom aacsearch import AACSearchClient, AACSearchError
client = AACSearchClient("aac_live_9f3c2a1b…", base_url="https://search.example.com/api/v1")
result = client.search(index="documentation", query={"q": "preise", "per_page": 5})
print(result["found"], result["hits"][0]["document"]["title"])Konstruktor
AACSearchClient(
api_key: str,
base_url: str = "http://localhost:3000/api/v1",
max_retries: int = 2,
)Der Client hält eine requests.Session und sendet bei jedem Aufruf Authorization: Bearer <key> plus Content-Type: application/json. Ein leerer api_key wirft vor dem Request:
AACSearchClient("")
# AACSearchError: api_key is required (status=401, code=MISSING_API_KEY)Fehler
class AACSearchError(Exception):
status: int # HTTP-Status
code: str # UNAUTHORIZED, QUOTA_EXCEEDED, VALIDATION_ERROR …
details: Any # details aus dem Fehler-Umschlag
request_id: str | Nonetry:
client.upsert_documents(
index="documentation",
body={"documents": [{"external_id": "pricing", "content": "Pro kostet 29 USD im Monat."}]},
)
except AACSearchError as error:
if error.code == "RATE_LIMITED":
time.sleep(2)
elif error.code == "QUOTA_EXCEEDED":
raise SystemExit(f"Kontingent erschöpft bis {(error.details or {}).get('resetsAt')}")
else:
raise RuntimeError(f"{error.code} {error} (request {error.request_id})")__str__ liefert die message aus dem Umschlag, deshalb liest sich str(error) wie ein Satz und nicht wie ein Statuscode.
Retries
Der Client enthält einen Helfer _request, der 429 und 5xx bis max_retries mit 250 ms × 2ⁿ Backoff wiederholt. Die generierten Methoden rufen requests jedoch direkt auf und laufen nicht durch diesen Helfer — im aktuellen Build ist daher kein automatischer Retry anzunehmen: umwickeln Sie wichtige Aufrufe mit eigenem Retry samt Jitter und beachten Sie, dass 429 ohne Retry-After kommt; lesen Sie bei Bedarf error.details["reset_at"].
Methoden
Alle Methoden sind nach self keyword-only, nutzen snake_case und geben das geparste JSON als dict zurück. query-Argumente werden Query-Parameter, body-Argumente der JSON-Body.
Suche und Discovery
| Methode | HTTP |
|---|---|
search(index=..., query={...}) | GET /indexes/{index}/search |
search_post(index=..., body={...}) | POST /indexes/{index}/search |
suggest(index=..., query={...}) | GET /indexes/{index}/suggest |
multi_search(body={...}) | POST /indexes/multi_search |
nl_search(index=..., body={...}) | POST /indexes/{index}/nl-search |
index_stats(index=..., query={...}) | GET /indexes/{index}/stats |
health(), capabilities(), openapi() | öffentliche Discovery-Endpunkte |
metrics() | GET /metrics (Betriebs-Token nötig) |
hits = client.search(
index="documentation",
query={"q": "preise", "query_by": "title,content", "filter_by": "tags:=[billing]", "per_page": 5},
)
for hit in hits["hits"]:
print(hit["document"]["title"], hit["score"])Dokumente
| Methode | HTTP |
|---|---|
upsert_documents(index=..., body={...}) | POST /indexes/{index}/documents |
get_document(index=..., externalId=...) | GET /indexes/{index}/documents/{externalId} |
delete_document(index=..., externalId=...) | DELETE /indexes/{index}/documents/{externalId} |
delete_documents(index=..., query={...}) | DELETE /indexes/{index}/documents?filter_by=… |
bulk_update_documents(index=..., body={...}) | POST /indexes/{index}/documents/bulk |
export_documents(index=...) | GET /indexes/{index}/documents/export |
Beachten Sie externalId in camelCase — die einzige Stelle, an der die generierte Signatur den Protokollnamen behalten hat.
client.upsert_documents(
index="documentation",
body={
"documents": [
{
"external_id": "pricing",
"title": "Preise",
"content": "Pro kostet 29 USD im Monat und enthält 500.000 Suchen.",
"tags": ["billing"],
}
],
"action": "upsert",
},
)
document = client.get_document(index="documentation", externalId="pricing")
client.delete_documents(index="documentation", query={"filter_by": "source_type:=connector"})KI
| Methode | HTTP |
|---|---|
answer(index=..., body={...}) | POST /indexes/{index}/answer |
assistant_ask(index=..., body={...}) | POST /indexes/{index}/assistant |
assistant_stream(index=..., body={...}) | POST /indexes/{index}/assistant/stream — Generator |
assistant_native(index=..., body={...}) | POST /indexes/{index}/assistant/native |
assistant_threads(index=...) | GET /indexes/{index}/assistant |
assistant_thread(index=..., conversationId=...) | GET /indexes/{index}/assistant/{conversationId} |
conversation_models(index=...) | GET /indexes/{index}/conversation-models |
voice_search(index=..., body={...}) | POST /indexes/{index}/voice |
image_search(index=..., body={...}) | POST /indexes/{index}/image |
answer = client.answer(index="documentation", body={"question": "Was kostet Pro?"})
print(answer["answer"])
for citation in answer["citations"]:
print(f"[{citation['index']}] {citation['title']} {citation['url'] or ''}")
for event in client.assistant_stream(index="documentation", body={"question": "Und die Limits?"}):
if event["type"] == "delta":
print(event["payload"].get("text", ""), end="")assistant_stream ist ein Generator über geparste SSE-Frames: {"type": "citations" | "delta" | "done" | "error", "payload": …}.
Relevanz, Betrieb, Analytics
| Methode | HTTP |
|---|---|
list_synonyms / put_synonym / delete_synonym | /indexes/{index}/synonyms |
list_curations / put_curation / delete_curation | /indexes/{index}/curations |
track_event(index=..., body={...}) | POST /indexes/{index}/events |
reindex(index=...) | POST /indexes/{index}/reindex |
scoped_key(index=..., body={...}) | POST /indexes/{index}/scoped-key |
usage(), wallet() | /billing/usage, /billing/wallet |
analytics_overview(query={...}) | GET /analytics/overview |
analytics_overview_csv(query={...}) | GET /analytics/overview.csv |
slow_queries(query={...}), trending_queries(query={...}) | /analytics/slow-queries, /analytics/trending |
export_organization(), purge_organization(query={...}) | /organization/export, /organization/data |
trigger_snapshot(), run_jobs(query={...}) | /admin/snapshot, /admin/jobs/run |
Vorbehalte
Der generierte Python-Client übernimmt auch die Abweichungen des TypeScript-Clients vom Server:
multi_search(body={...})sendet an die föderierte Route/indexes/multi_searchund liefert ein Ergebnis pro Suche.export_documentsruftresponse.json()auf einem NDJSON-Stream auf und wirft. Lesen Sieresponse.text.webhook_ingestsendet weder Body noch Signatur-Header; signieren Sie selbst und nutzen Sierequests.multi_suggestsendet keinen Body, obwohl der Endpunktindexesundqverlangt.
Für alles außerhalb der generierten Oberfläche sind die rohen Endpunkte der API-Referenz der Vertrag — der Client fügt nichts hinzu außer Basis-URL-Handling und JSON-Kodierung.