AACSearch
SDK

TypeScript SDK

Сгенерированный клиент @repo/sdk — параметры конструктора, все методы, поведение повторов и тип AACSearchError.

@repo/sdk — типизированный клиент, сгенерированный из packages/api/routes/v1/openapi.json (пересборка — pnpm sdk:generate). Это ESM-пакет, использующий глобальный fetch, поэтому он работает в Node 18+, Bun, Deno и на edge-рантаймах.

import { AACSearchClient, AACSearchError } from "@repo/sdk";

const client = new AACSearchClient({
	apiKey: process.env.AAC_KEY!,
	baseUrl: "https://search.example.com/api/v1",
});

const result = await client.search("documentation", { q: "тарифы", per_page: 5 });
console.log(result.found, result.hits[0]?.document.title);

Конструктор

interface AACSearchClientOptions {
	/** Ключ aac_live_… Обязателен. */
	apiKey: string;
	/** По умолчанию http://localhost:3000/api/v1 */
	baseUrl?: string;
	/** Повторы для 429 и 5xx. По умолчанию 2. */
	maxRetries?: number;
	/** Своя реализация fetch — для трассировки или прокси. */
	fetch?: typeof fetch;
}

Пустой apiKey приводит к исключению сразу, до первого запроса:

new AACSearchClient({ apiKey: "" });
// AACSearchError: apiKey is required (status 401, code MISSING_API_KEY)

Завершающий слэш в baseUrl отбрасывается, поэтому https://host/api/v1/ и https://host/api/v1 эквивалентны.

Ошибки и повторы

Любая ошибка — это AACSearchError с разобранным конвертом и HTTP-статусом:

class AACSearchError extends Error {
	name: "AACSearchError";
	status: number; // 401, 403, 422, 429, 502…
	code: string; // UNAUTHORIZED, QUOTA_EXCEEDED, VALIDATION_ERROR…
	details?: unknown; // details из тела ошибки
	requestId?: string; // указывайте в обращении в поддержку
}
try {
	await client.upsertDocuments("documentation", {
		documents: [{ external_id: "pricing", content: "Pro стоит 29 USD в месяц." }],
		action: "create",
	});
} catch (error) {
	if (error instanceof AACSearchError) {
		if (error.code === "QUOTA_EXCEEDED") {
			console.error("Квота исчерпана, ждать до", (error.details as { resetsAt: string }).resetsAt);
		} else if (error.status === 429) {
			// сработал лимит запросов — SDK уже исчерпал maxRetries
		} else {
			console.error(error.code, error.message, error.requestId);
		}
	}
	throw error;
}

Повторы: 429 и любые 5xx повторяются до maxRetries раз (по умолчанию 2) с экспоненциальной задержкой 250 мс, 500 мс, 1 с. Остальные 4xx не повторяются никогда. Когда попытки исчерпаны, разбирается последний ответ и бросается исключение. Если в теле нет объекта error, code становится REQUEST_FAILED, а сообщение — Request failed with <status>.

Таймаут клиент не навязывает: передайте свой fetch, учитывающий AbortSignal, если он нужен.

Методы

Параметры и тела — обычные объекты; клиент сериализует только определённые и непустые значения, поэтому undefined и "" не превращаются в пустые параметры.

Поиск и обнаружение

МетодHTTP
search(index, query)GET /indexes/{index}/search
searchPost(index, body)POST /indexes/{index}/search
suggest(index, query)GET /indexes/{index}/suggest
multiSearch(body)POST /indexes/multi_search
nlSearch(index, body)POST /indexes/{index}/nl-search
indexStats(index, query?)GET /indexes/{index}/stats
health()GET /health
capabilities()GET /capabilities
openapi()GET /openapi.json
metrics()GET /metrics
const suggestions = await client.suggest("documentation", { q: "тар", limit: 5 });
const stats = await client.indexStats("documentation");
const caps = await client.capabilities();
if (!caps.features.autoEmbedding) throw new Error("семантический поиск недоступен на этом узле");

Документы

МетодHTTP
upsertDocuments(index, body)POST /indexes/{index}/documents
getDocument(index, externalId)GET /indexes/{index}/documents/{externalId}
deleteDocument(index, externalId)DELETE /indexes/{index}/documents/{externalId}
deleteDocuments(index, query)DELETE /indexes/{index}/documents?filter_by=…
bulkUpdateDocuments(index, body)POST /indexes/{index}/documents/bulk
exportDocuments(index)GET /indexes/{index}/documents/export
await client.upsertDocuments("documentation", {
	documents: [
		{
			external_id: "pricing",
			title: "Тарифы",
			content: "Pro стоит 29 USD в месяц и включает 500 000 поисков.",
			tags: ["billing"],
		},
	],
	action: "upsert",
});

const doc = await client.getDocument("documentation", "pricing");
await client.deleteDocument("documentation", "pricing");
await client.deleteDocuments("documentation", { filter_by: "source_type:=connector" });

AI

МетодHTTP
answer(index, body)POST /indexes/{index}/answer — ответ RAG со ссылками
assistantAsk(index, body)POST /indexes/{index}/assistant — шаг диалога
assistantStream(index, body, onEvent)POST /indexes/{index}/assistant/stream — SSE
assistantNative(index, body)POST /indexes/{index}/assistant/native — модель диалога движка
assistantThreads(index)GET /indexes/{index}/assistant
assistantThread(index, conversationId)GET /indexes/{index}/assistant/{conversationId}
conversationModels(index)GET /indexes/{index}/conversation-models
voiceSearch(index, body)POST /indexes/{index}/voice
imageSearch(index, body)POST /indexes/{index}/image
const answer = await client.answer("documentation", {
	question: "Сколько стоит тариф pro?",
	per_page: 4,
});
console.log(answer.answer, answer.citations);

await client.assistantStream("documentation", { question: "А какие лимиты?" }, (event) => {
	if (event.type === "delta")
		process.stdout.write(String((event.payload as { text?: string }).text ?? ""));
});

assistantStream сам читает тело ответа, разбивает кадры по пустой строке, разбирает каждую строку data: и передаёт вам { type, payload }. Типы событий: citations, delta, done, error. Промис завершается при закрытии потока; пустое тело даёт AACSearchError с кодом SSE_EMPTY.

Релевантность и операции

МетодHTTP
listSynonyms(index) / putSynonym(index, body) / deleteSynonym(index, id)/indexes/{index}/synonyms
listCurations(index) / putCuration(index, body) / deleteCuration(index, id)/indexes/{index}/curations
trackEvent(index, body)POST /indexes/{index}/events
reindex(index)POST /indexes/{index}/reindex
scopedKey(index, body)POST /indexes/{index}/scoped-key
triggerSnapshot()POST /admin/snapshot
runJobs(query)POST /admin/jobs/run
exportOrganization()GET /organization/export
purgeOrganization(query)DELETE /organization/data

Аналитика и биллинг

МетодHTTP
analyticsOverview(query)GET /analytics/overview
analyticsOverviewCsv(query)GET /analytics/overview.csv
slowQueries(query)GET /analytics/slow-queries
trendingQueries(query)GET /analytics/trending
usage()GET /billing/usage
wallet()GET /billing/wallet
const { overview } = await client.analyticsOverview({ days: 7 });
console.log(overview.totals.searches, overview.totals.zeroResultRate);

const { usage } = await client.usage();
console.log(`${usage.searches.used}/${usage.searches.limit} поисков за период`);

Известные расхождения генератора

Клиент генерируется механически из документа OpenAPI, и три метода не совпадают с сервером. Они перечислены здесь, чтобы вы не потратили на них полдня:

МетодПроблемаОбход
multiSuggest()Не отправляет тело, хотя эндпоинту нужны { indexes, q }fetch на POST /indexes/suggest/multi
exportDocuments(index)Разбирает NDJSON-поток через response.json()fetch на экспорт и чтение текста
webhookIngest(index)Не отправляет ни тело, ни заголовки подписиПодпишите и отправьте сами, см. Вебхуки

Остальные методы соответствуют документированным эндпоинтам один к одному.

On this page