AACSearch
SDK

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 | None
try:
    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.

On this page