Python SDK
Сгенерированный клиент aacsearch — установка, конструктор, методы только с именованными аргументами, AACSearchError и оговорка про повторы.
Python-клиент лежит в packages/sdk-python, имя дистрибутива — aacsearch, пакет — aacsearch. Нужен Python 3.9+ и requests>=2.31; клиент генерируется из того же документа OpenAPI, что и TypeScript-версия.
pip install -e packages/sdk-python # внутри монорепозиторияfrom aacsearch import AACSearchClient, AACSearchError
client = AACSearchClient("aac_live_9f3c2a1b…", base_url="https://search.example.com/api/v1")
result = client.search(index="documentation", query={"q": "тарифы", "per_page": 5})
print(result["found"], result["hits"][0]["document"]["title"])Конструктор
AACSearchClient(
api_key: str,
base_url: str = "http://localhost:3000/api/v1",
max_retries: int = 2,
)Клиент держит requests.Session и отправляет Authorization: Bearer <key> и Content-Type: application/json с каждым вызовом. Пустой api_key приводит к исключению до запроса:
AACSearchClient("")
# AACSearchError: api_key is required (status=401, code=MISSING_API_KEY)Ошибки
class AACSearchError(Exception):
status: int # HTTP-статус
code: str # UNAUTHORIZED, QUOTA_EXCEEDED, VALIDATION_ERROR…
details: Any # details из конверта ошибки
request_id: str | Nonetry:
client.upsert_documents(
index="documentation",
body={"documents": [{"external_id": "pricing", "content": "Pro стоит 29 USD в месяц."}]},
)
except AACSearchError as error:
if error.code == "RATE_LIMITED":
time.sleep(2)
elif error.code == "QUOTA_EXCEEDED":
raise SystemExit(f"Квота исчерпана до {(error.details or {}).get('resetsAt')}")
else:
raise RuntimeError(f"{error.code} {error} (request {error.request_id})")__str__ возвращает message из конверта, поэтому str(error) читается как фраза, а не как код состояния.
Повторы
В клиенте есть вспомогательный метод _request, который повторяет 429 и 5xx до max_retries раз с задержкой 250 мс × 2ⁿ. Но сгенерированные методы обращаются к requests напрямую и не проходят через него, поэтому в текущей сборке считайте, что автоматических повторов нет: оборачивайте важные вызовы в свой retry с джиттером и учитывайте, что 429 приходит без Retry-After — при наличии читайте error.details["reset_at"].
Методы
Все методы после self принимают только именованные аргументы, используют snake_case и возвращают разобранный JSON как dict. Аргумент query превращается в параметры строки запроса, body — в тело JSON.
Поиск и обнаружение
| Метод | 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() | публичные эндпоинты обнаружения |
metrics() | GET /metrics (нужен операционный токен) |
hits = client.search(
index="documentation",
query={"q": "тарифы", "query_by": "title,content", "filter_by": "tags:=[billing]", "per_page": 5},
)
for hit in hits["hits"]:
print(hit["document"]["title"], hit["score"])Документы
| Метод | 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 |
Обратите внимание на externalId в camelCase — это единственное место, где сгенерированная сигнатура сохранила имя из протокола.
client.upsert_documents(
index="documentation",
body={
"documents": [
{
"external_id": "pricing",
"title": "Тарифы",
"content": "Pro стоит 29 USD в месяц и включает 500 000 поисков.",
"tags": ["billing"],
}
],
"action": "upsert",
},
)
document = client.get_document(index="documentation", externalId="pricing")
client.delete_documents(index="documentation", query={"filter_by": "source_type:=connector"})AI
| Метод | 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 — генератор событий |
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": "Сколько стоит 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": "А лимиты?"}):
if event["type"] == "delta":
print(event["payload"].get("text", ""), end="")assistant_stream — генератор по разобранным SSE-кадрам: {"type": "citations" | "delta" | "done" | "error", "payload": …}.
Релевантность, операции, аналитика
| Метод | 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 |
Оговорки
Сгенерированный Python-клиент повторяет и расхождения TypeScript-версии с сервером:
multi_search(body={...})отправляет запрос на федеративный маршрут/indexes/multi_searchи возвращает по одному результату на каждый поиск.export_documentsвызываетresponse.json()на NDJSON-потоке и упадёт. Читайтеresponse.text.webhook_ingestне отправляет ни тела, ни заголовков подписи; подпишите полезную нагрузку сами и используйтеrequests.multi_suggestне отправляет тело, хотя эндпоинту нужныindexesиq.
Для всего, что выходит за пределы сгенерированной поверхности, контрактом остаются «сырые» эндпоинты из справочника API: клиент не добавляет поведения, кроме работы с базовым URL и кодирования JSON.