@takoviz/ai-sdk 2.0.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -25
- package/dist/index.d.ts +123 -112
- package/dist/index.js +110 -78
- package/package.json +14 -4
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install @takoviz/ai-sdk ai
|
|
|
10
10
|
|
|
11
11
|
## Setup
|
|
12
12
|
|
|
13
|
-
Get an API key from the [Tako developer console](https://
|
|
13
|
+
Get an API key from the [Tako developer console](https://tako.com/console/api-keys) and set it as an environment variable:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
export TAKO_API_KEY=your_api_key_here
|
|
@@ -32,7 +32,7 @@ import { openai } from '@ai-sdk/openai';
|
|
|
32
32
|
import { generateText, isStepCount } from 'ai';
|
|
33
33
|
|
|
34
34
|
const { text } = await generateText({
|
|
35
|
-
model: openai('gpt-
|
|
35
|
+
model: openai('gpt-5.4-mini'),
|
|
36
36
|
prompt: 'Did AMD or Nvidia grow headcount faster over the last decade?',
|
|
37
37
|
tools: { tako_answer: takoAnswer() },
|
|
38
38
|
stopWhen: isStepCount(5),
|
|
@@ -55,38 +55,68 @@ const tools = {
|
|
|
55
55
|
|
|
56
56
|
## Configuration
|
|
57
57
|
|
|
58
|
-
`
|
|
58
|
+
A tool's config is the API request body for its endpoint, minus the field the model supplies, plus `apiKey` and `baseUrl`. Keys are the API's own names, exactly as [`tako-sdk`](https://www.npmjs.com/package/tako-sdk) declares them, so every option in the [API reference](https://docs.tako.com) works here without a release of this package.
|
|
59
|
+
|
|
60
|
+
| Tool | Config type | Is the request body of | Minus |
|
|
61
|
+
| --- | --- | --- | --- |
|
|
62
|
+
| `takoSearch` | `TakoRetrievalConfig` | `POST /api/v3/search` | `query` |
|
|
63
|
+
| `takoAnswer` | `TakoAnswerConfig` | `POST /api/v1/answer` | `query` |
|
|
64
|
+
| `takoContents` | `TakoContentsConfig` | `POST /api/v1/contents` | `url` |
|
|
65
|
+
|
|
66
|
+
Every field is optional. Omit one and the API's default applies; this package restates none of them. The model supplies only `{ query }` or `{ url }` per call.
|
|
67
|
+
|
|
68
|
+
Three `sources.data` keys aren't exposed, because you can't use them correctly from here: `mode` (the API documents it as having no effect on Tako cards), and `node_ids` with `strict` (they take graph ids from endpoints this package doesn't wrap). Call the API through `tako-sdk` directly if you need them.
|
|
69
|
+
|
|
70
|
+
### Examples
|
|
71
|
+
|
|
72
|
+
Deep search over Tako's data only, ten cards, with the rows inlined as typed columns:
|
|
59
73
|
|
|
60
74
|
```typescript
|
|
61
75
|
takoSearch({
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
forceRefresh: false, // instant mode only
|
|
75
|
-
},
|
|
76
|
+
effort: 'deep',
|
|
77
|
+
sources: { data: { count: 10, include_contents: true, content_format: 'json_compact' } },
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
News from the last week. Build dates with the ISO-string constructor: `new Date('2026-08-19')` is UTC midnight and serializes as that day everywhere; `new Date(2026, 7, 19)` is local midnight and serializes as the day before in any UTC+ timezone.
|
|
82
|
+
|
|
83
|
+
Check that your date parsed before you pass it. `new Date()` returns an `Invalid Date` for a string it can't read, and that fails during serialization, so the tool call rejects with `Failed to search with Tako: Invalid time value` — a message that names neither the field nor the value, and reaches the model rather than you. The request never leaves the process.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
takoSearch({
|
|
87
|
+
sources: { web: { category: 'news', published_after: new Date('2026-08-19'), count: 5 } },
|
|
76
88
|
});
|
|
77
89
|
```
|
|
78
90
|
|
|
79
|
-
`
|
|
91
|
+
An answer shaped by a JSON Schema. Tako fills it from the same evidence as `answer` and returns it as `structured_output`; a nullable type is how you let it say "no evidence" instead of inventing a zero.
|
|
80
92
|
|
|
81
93
|
```typescript
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
takoAnswer({
|
|
95
|
+
output_schema: {
|
|
96
|
+
type: 'object',
|
|
97
|
+
properties: {
|
|
98
|
+
revenue_usd: { type: ['number', 'null'], description: 'Latest annual revenue in USD' },
|
|
99
|
+
fiscal_year: { type: ['integer', 'null'] },
|
|
100
|
+
},
|
|
101
|
+
required: ['revenue_usd', 'fiscal_year'],
|
|
102
|
+
additionalProperties: false,
|
|
103
|
+
},
|
|
86
104
|
});
|
|
87
105
|
```
|
|
88
106
|
|
|
89
|
-
|
|
107
|
+
A `TakoRetrievalConfig` is also a valid `TakoAnswerConfig`, so one object can build both tools.
|
|
108
|
+
|
|
109
|
+
### Contents delivery
|
|
110
|
+
|
|
111
|
+
Two `takoContents` options also change the description the model reads, so pick them deliberately:
|
|
112
|
+
|
|
113
|
+
- `mode: 'url'` returns a short-lived presigned download link. The description tells the model to surface the link, not parse it.
|
|
114
|
+
- `mode: 'inline'` returns the rows or page text in the response. The description tells the model to read and compute over them.
|
|
115
|
+
- `quote_only: true` returns the export price and no content, for free. The description tells the model to report the price and not call again expecting rows.
|
|
116
|
+
|
|
117
|
+
Leave `mode` unset and the API chooses — `'url'` today. This package sends no default and names none in the description; the model is told to read the response instead. Set `mode` to pin the delivery and tell the model which one to expect.
|
|
118
|
+
|
|
119
|
+
`max_rows` fails quietly: a value over the 2,000-row ceiling is clamped, not rejected, and every row returned is billed. Read `total_rows` and `truncated` on the item to see what you got.
|
|
90
120
|
|
|
91
121
|
## Responses
|
|
92
122
|
|
|
@@ -96,12 +126,40 @@ The LLM supplies only the dynamic input: `{ query }` for `takoSearch`/`takoAnswe
|
|
|
96
126
|
{
|
|
97
127
|
cards: TakoCard[]; // Tako knowledge cards (title, description, image_url, webpage_url, sources, ...)
|
|
98
128
|
web_results: TakoWebResult[];
|
|
99
|
-
contents_total_cost: number;
|
|
100
129
|
request_id: string;
|
|
130
|
+
usage?: TakoUsage | null; // { total_cost_usd, compute?, data? } — see note
|
|
101
131
|
}
|
|
102
132
|
```
|
|
103
133
|
|
|
104
|
-
|
|
134
|
+
> **Cost reporting.** `usage` is what the API spec defines for per-request cost, but as of 2026-08 it is not populated on any endpoint. For pricing today, read the per-item `content.cost` and `content.export_pricing` on each card, which are populated.
|
|
135
|
+
|
|
136
|
+
`takoAnswer` additionally includes `answer: string` (with `cards[0]` as the lead card). `takoContents` resolves to `{ contents: TakoContentItem[]; request_id: string; usage? }`.
|
|
137
|
+
|
|
138
|
+
The API guarantees only `request_id` — the contract permits omitting the collections — so the tools normalize: `cards`, `web_results` and `contents` are **always arrays**. No `?.` needed.
|
|
139
|
+
|
|
140
|
+
### Reading a card
|
|
141
|
+
|
|
142
|
+
Two fields are worth knowing about:
|
|
143
|
+
|
|
144
|
+
- **`exportable`** — whether `takoContents` can download that card's data. `false` means don't bother; the call returns 403. `true` is eligibility, not a guarantee, so still handle errors.
|
|
145
|
+
- **`data_freshness`** — `{ data_as_of, last_updated }`, so you can tell how current a number is.
|
|
146
|
+
|
|
147
|
+
### Reading a contents item
|
|
148
|
+
|
|
149
|
+
Each item carries a `cost` (USD) and either a presigned `url` + `expires_at` (url mode) or an inline payload (inline mode). `content_format` tells you what you got:
|
|
150
|
+
|
|
151
|
+
| `content_format` | Payload field | Meaning |
|
|
152
|
+
| --- | --- | --- |
|
|
153
|
+
| `null` *or absent* | `data` | A web page's extracted text |
|
|
154
|
+
| `'csv'` | `data` | Card data as CSV |
|
|
155
|
+
| `'json_records'` | `records` | Card data as row objects |
|
|
156
|
+
| `'json_compact'` | `dataset` | Card data as typed columns + positional rows |
|
|
157
|
+
|
|
158
|
+
`total_rows` and `truncated` tell you whether the card held more rows than were returned.
|
|
159
|
+
|
|
160
|
+
Which format you get depends on the surface. Left unset, `takoContents` returns `'csv'` for cards and no format for web pages, while a card inlined by `sources.data.include_contents` arrives as `'json_compact'` (a `dataset`). Set `content_format` to choose: on `takoContents` for an explicit fetch, or on `sources.data` for a card inlined by a search.
|
|
161
|
+
|
|
162
|
+
`content_format` is optional as well as nullable, so branch on it loosely — `content_format == null` means web text; `=== null` misses the absent case.
|
|
105
163
|
|
|
106
164
|
Full type definitions ship with the package.
|
|
107
165
|
|
|
@@ -110,22 +168,34 @@ Full type definitions ship with the package.
|
|
|
110
168
|
```typescript
|
|
111
169
|
import type {
|
|
112
170
|
TakoRetrievalConfig,
|
|
171
|
+
TakoAnswerConfig,
|
|
113
172
|
TakoContentsConfig,
|
|
114
173
|
TakoSearchResult,
|
|
115
174
|
TakoAnswerResult,
|
|
116
175
|
TakoContentsResult,
|
|
117
176
|
TakoCard,
|
|
177
|
+
TakoCardSource,
|
|
118
178
|
TakoWebResult,
|
|
119
179
|
TakoContentItem,
|
|
180
|
+
TakoDataset,
|
|
181
|
+
TakoUsage,
|
|
120
182
|
} from '@takoviz/ai-sdk';
|
|
121
183
|
```
|
|
122
184
|
|
|
185
|
+
Every wire type is [`tako-sdk`](https://www.npmjs.com/package/tako-sdk)'s, Tako's client generated from the OpenAPI spec, re-exported under the names above. This package declares only its config types and the normalized tool results. When the API adds a field, it appears here as soon as `tako-sdk` publishes — no release of this package needed.
|
|
186
|
+
|
|
187
|
+
`tako-sdk` is a runtime dependency. It uses the global `fetch` and runs wherever this package does.
|
|
188
|
+
|
|
189
|
+
If you need the raw wire shapes (where collections are optional, before the tools normalize them), import `TakoSearchResponse`, `TakoAnswerResponse` or `TakoContentsResponse`.
|
|
190
|
+
|
|
123
191
|
## License
|
|
124
192
|
|
|
125
193
|
MIT
|
|
126
194
|
|
|
127
195
|
## Links
|
|
128
196
|
|
|
197
|
+
- [Migrating from 3.x](./MIGRATING.md#3x--40)
|
|
198
|
+
- [Migrating from 2.x](./MIGRATING.md#2x--30)
|
|
129
199
|
- [Tako documentation](https://docs.tako.com)
|
|
130
200
|
- [Vercel AI SDK](https://sdk.vercel.ai/docs)
|
|
131
201
|
- [GitHub repository](https://github.com/TakoData/ai-sdk)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,141 +1,152 @@
|
|
|
1
1
|
import { Tool } from 'ai';
|
|
2
|
+
import * as sdk from 'tako-sdk';
|
|
2
3
|
|
|
3
|
-
type TakoSearchEffort =
|
|
4
|
-
type TakoContentsMode =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type
|
|
4
|
+
type TakoSearchEffort = sdk.SearchEffortLevel;
|
|
5
|
+
type TakoContentsMode = sdk.ContentsDeliveryMode;
|
|
6
|
+
/** Serialization of tabular (Tako card) data. Web text carries no format. */
|
|
7
|
+
type TakoContentFormat = sdk.ContentsFormat;
|
|
8
|
+
/** Public source taxonomy for the card surfaces. */
|
|
9
|
+
type TakoSourceIndex = sdk.TakoSourceIndex;
|
|
10
|
+
type TakoKnowledgeCardRelevance = sdk.KnowledgeCardRelevance;
|
|
11
|
+
type TakoGraphNodeType = sdk.GraphNodeType;
|
|
12
|
+
type TakoDatasetColumnType = sdk.TakoDatasetColumnType;
|
|
13
|
+
/** Web result category. Only "news" filters today; the others are accepted and inert. */
|
|
14
|
+
type TakoWebCategory = sdk.WebCategory;
|
|
9
15
|
interface TakoBaseConfig {
|
|
10
16
|
/** Tako API key. Falls back to TAKO_API_KEY / TAKO_API_TOKEN env vars. */
|
|
11
17
|
apiKey?: string;
|
|
12
18
|
/** API base URL. Default "https://tako.com". */
|
|
13
19
|
baseUrl?: string;
|
|
14
20
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
interface
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
interface TakoContentItem extends TakoResultContent {
|
|
102
|
-
source_url: string;
|
|
103
|
-
url?: string | null;
|
|
104
|
-
expires_at?: string | null;
|
|
105
|
-
}
|
|
106
|
-
interface TakoSearchResult {
|
|
21
|
+
/**
|
|
22
|
+
* The curated Tako data source. Three `DataSourceSettings` keys are omitted
|
|
23
|
+
* because you can't use them correctly from this package:
|
|
24
|
+
*
|
|
25
|
+
* - `mode` — the API documents it as having no effect on Tako cards, which
|
|
26
|
+
* always inline rows. Setting `"url"` looks like it should return download
|
|
27
|
+
* links and doesn't.
|
|
28
|
+
* - `node_ids` — takes ids from the `/v1/graph` endpoints, which this package
|
|
29
|
+
* doesn't wrap, and the ids don't survive a knowledge-graph rebuild.
|
|
30
|
+
* - `strict` — only meaningful with `node_ids`.
|
|
31
|
+
*
|
|
32
|
+
* The omission is a type. The API accepts the keys if you send them anyway.
|
|
33
|
+
*/
|
|
34
|
+
type TakoDataSourceOptions = Omit<sdk.DataSourceSettings, "mode" | "node_ids" | "strict">;
|
|
35
|
+
/** The web source. Every `WebSourceSettings` key, unchanged. */
|
|
36
|
+
type TakoWebSourceOptions = sdk.WebSourceSettings;
|
|
37
|
+
/**
|
|
38
|
+
* Tako searches exactly the sources whose keys are present. Omit `sources` to
|
|
39
|
+
* search data and web.
|
|
40
|
+
*
|
|
41
|
+
* Derived from `sdk.Sources`, not a literal `{ data, web }` pair. Spelling the
|
|
42
|
+
* pair out is the one edit that silently breaks this file's promise: a source
|
|
43
|
+
* Tako adds would serialize on the wire and still be unreachable here, and the
|
|
44
|
+
* `keyof` pin in `config_types.test.ts` would stay green because a hand-written
|
|
45
|
+
* source set is what forced it to exclude `sources` in the first place.
|
|
46
|
+
*/
|
|
47
|
+
interface TakoSources extends Omit<sdk.Sources, "data"> {
|
|
48
|
+
data?: TakoDataSourceOptions;
|
|
49
|
+
}
|
|
50
|
+
/** Config for `takoSearch`: the `/v3/search` request body without `query`. */
|
|
51
|
+
interface TakoRetrievalConfig extends TakoBaseConfig, Omit<sdk.SearchRequest, "query" | "sources"> {
|
|
52
|
+
sources?: TakoSources;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Config for `takoAnswer`: the `/v1/answer` request body without `query`.
|
|
56
|
+
* A superset of {@link TakoRetrievalConfig}, so one config object can build
|
|
57
|
+
* both tools. Adds `output_schema`, a JSON Schema Tako fills from the same
|
|
58
|
+
* evidence as `answer` and returns as `structured_output`.
|
|
59
|
+
*
|
|
60
|
+
* `output_schema` needs `effort` `"fast"` or `"deep"`. `takoAnswer` throws on
|
|
61
|
+
* the pair rather than letting every call 400.
|
|
62
|
+
*/
|
|
63
|
+
interface TakoAnswerConfig extends TakoBaseConfig, Omit<sdk.AnswerRequest, "query" | "sources"> {
|
|
64
|
+
sources?: TakoSources;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Config for `takoContents`: the `/v1/contents` request body without `url`.
|
|
68
|
+
* `mode` and `quote_only` also change the tool description the model reads.
|
|
69
|
+
*/
|
|
70
|
+
interface TakoContentsConfig extends TakoBaseConfig, Omit<sdk.ContentsRequest, "url"> {
|
|
71
|
+
}
|
|
72
|
+
type TakoUsageCompute = sdk.UsageCompute;
|
|
73
|
+
type TakoUsageData = sdk.UsageData;
|
|
74
|
+
/**
|
|
75
|
+
* Usage for one metered request. As of 2026-08 the API doesn't populate it on
|
|
76
|
+
* search, answer or contents. For per-item pricing today, read
|
|
77
|
+
* `TakoResultContent.cost` and `TakoResultContent.export_pricing`.
|
|
78
|
+
*/
|
|
79
|
+
type TakoUsage = sdk.Usage;
|
|
80
|
+
type TakoDatasetColumn = sdk.TakoDatasetColumn;
|
|
81
|
+
type TakoDatasetSource = sdk.TakoDatasetSource;
|
|
82
|
+
type TakoDatasetCell = sdk.TakoDatasetCell;
|
|
83
|
+
type TakoDataset = sdk.TakoDataset;
|
|
84
|
+
type TakoExportPricing = sdk.ExportPricing;
|
|
85
|
+
type TakoColumnDescriptor = sdk.ColumnDescriptor;
|
|
86
|
+
/**
|
|
87
|
+
* Describes the downloadable content behind a result. `content_format` is
|
|
88
|
+
* optional as well as nullable — web text may arrive as `null` or an absent key.
|
|
89
|
+
* Test it loosely (`content_format == null`), never with `=== null`.
|
|
90
|
+
*/
|
|
91
|
+
type TakoResultContent = sdk.ResultContent;
|
|
92
|
+
type TakoContentItem = sdk.ContentItem;
|
|
93
|
+
type TakoCardSource = sdk.TakoCardSource;
|
|
94
|
+
type TakoKnowledgeCardMethodology = sdk.KnowledgeCardMethodology;
|
|
95
|
+
type TakoCardNode = sdk.TakoCardNode;
|
|
96
|
+
type TakoMetricDefinition = sdk.MetricDefinition;
|
|
97
|
+
type TakoDataFreshness = sdk.DataFreshness;
|
|
98
|
+
type TakoCard = sdk.TakoCard;
|
|
99
|
+
type TakoWebResult = sdk.WebResult;
|
|
100
|
+
/** The raw `POST /api/v3/search` body. Only `request_id` is guaranteed. */
|
|
101
|
+
type TakoSearchResponse = sdk.SearchResponse;
|
|
102
|
+
/** The raw `POST /api/v1/answer` body. */
|
|
103
|
+
type TakoAnswerResponse = sdk.AnswerResponse;
|
|
104
|
+
/** The raw `POST /api/v1/contents` body. */
|
|
105
|
+
type TakoContentsResponse = sdk.ContentsResponse;
|
|
106
|
+
type TakoSearchResult = Omit<TakoSearchResponse, "cards" | "web_results"> & {
|
|
107
107
|
cards: TakoCard[];
|
|
108
108
|
web_results: TakoWebResult[];
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
interface TakoAnswerResult {
|
|
109
|
+
};
|
|
110
|
+
type TakoAnswerResult = Omit<TakoAnswerResponse, "cards" | "web_results"> & {
|
|
113
111
|
/** Synthesized text answer. */
|
|
114
112
|
answer: string;
|
|
115
113
|
/** Backing cards; cards[0] is the lead card. */
|
|
116
114
|
cards: TakoCard[];
|
|
117
115
|
web_results: TakoWebResult[];
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
interface TakoContentsResult {
|
|
116
|
+
};
|
|
117
|
+
type TakoContentsResult = Omit<TakoContentsResponse, "contents"> & {
|
|
122
118
|
contents: TakoContentItem[];
|
|
123
|
-
|
|
124
|
-
}
|
|
119
|
+
};
|
|
125
120
|
|
|
126
121
|
/** Tako fast-pipeline search: returns Tako cards + web results, no LLM synthesis. */
|
|
127
122
|
declare function takoSearch(config?: TakoRetrievalConfig): Tool<{
|
|
128
123
|
query: string;
|
|
129
124
|
}, TakoSearchResult>;
|
|
130
125
|
|
|
131
|
-
/**
|
|
132
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Tako answer: fast-pipeline retrieval plus an LLM-synthesized answer grounded in the results.
|
|
128
|
+
*
|
|
129
|
+
* Resolves to `{ answer, cards, web_results, ... }` — `cards[0]` is the lead card, carrying
|
|
130
|
+
* the chart `image_url`/`embed_url` you can surface in your own UI.
|
|
131
|
+
*/
|
|
132
|
+
declare function takoAnswer(config?: TakoAnswerConfig): Tool<{
|
|
133
133
|
query: string;
|
|
134
134
|
}, TakoAnswerResult>;
|
|
135
135
|
|
|
136
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* Download the data behind a result URL: a Tako card's CSV or a web page's text.
|
|
138
|
+
*
|
|
139
|
+
* `mode` sets the delivery, and is reflected in the tool description the model reads:
|
|
140
|
+
* - `"url"` — a short-lived presigned download url. Use when handing a
|
|
141
|
+
* download/embed link to a user, or for large data you won't read yourself.
|
|
142
|
+
* - `"inline"` — the content in the response body, so the model can read and reason
|
|
143
|
+
* over the numbers directly.
|
|
144
|
+
*
|
|
145
|
+
* Left unset, the API chooses and the description tells the model to read the response.
|
|
146
|
+
* Set `mode` to pin the delivery and tell the model which one to expect.
|
|
147
|
+
*/
|
|
137
148
|
declare function takoContents(config?: TakoContentsConfig): Tool<{
|
|
138
149
|
url: string;
|
|
139
150
|
}, TakoContentsResult>;
|
|
140
151
|
|
|
141
|
-
export { type
|
|
152
|
+
export { type TakoAnswerConfig, type TakoAnswerResponse, type TakoAnswerResult, type TakoBaseConfig, type TakoCard, type TakoCardNode, type TakoCardSource, type TakoColumnDescriptor, type TakoContentFormat, type TakoContentItem, type TakoContentsConfig, type TakoContentsMode, type TakoContentsResponse, type TakoContentsResult, type TakoDataFreshness, type TakoDataSourceOptions, type TakoDataset, type TakoDatasetCell, type TakoDatasetColumn, type TakoDatasetColumnType, type TakoDatasetSource, type TakoExportPricing, type TakoGraphNodeType, type TakoKnowledgeCardMethodology, type TakoKnowledgeCardRelevance, type TakoMetricDefinition, type TakoResultContent, type TakoRetrievalConfig, type TakoSearchEffort, type TakoSearchResponse, type TakoSearchResult, type TakoSourceIndex, type TakoSources, type TakoUsage, type TakoUsageCompute, type TakoUsageData, type TakoWebCategory, type TakoWebResult, type TakoWebSourceOptions, takoAnswer, takoContents, takoSearch };
|
package/dist/index.js
CHANGED
|
@@ -3,33 +3,28 @@ import { tool } from "ai";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
// src/client.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
import { Tako } from "tako-sdk";
|
|
7
|
+
|
|
8
|
+
// src/errors.ts
|
|
9
|
+
import { FetchError, ResponseError } from "tako-sdk";
|
|
10
|
+
function isResponseError(error) {
|
|
11
|
+
return error instanceof ResponseError || error instanceof Error && error.name === "ResponseError" && "response" in error;
|
|
12
|
+
}
|
|
13
|
+
function isFetchError(error) {
|
|
14
|
+
return error instanceof FetchError || error instanceof Error && error.name === "FetchError" && "cause" in error;
|
|
15
|
+
}
|
|
16
|
+
async function wrapTakoError(error, operation) {
|
|
17
|
+
const prefix = `Failed to ${operation} with Tako: `;
|
|
18
|
+
if (isResponseError(error)) {
|
|
19
|
+
const body = await error.response.text().catch(() => "");
|
|
20
|
+
return new Error(`${prefix}Tako API error: ${error.response.status} - ${body}`, { cause: error });
|
|
12
21
|
}
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
headers: {
|
|
17
|
-
"Content-Type": "application/json",
|
|
18
|
-
"X-API-Key": apiKey
|
|
19
|
-
},
|
|
20
|
-
body: JSON.stringify(body)
|
|
21
|
-
});
|
|
22
|
-
if (!response.ok) {
|
|
23
|
-
const errorText = await response.text();
|
|
24
|
-
throw new Error(`Tako API error: ${response.status} - ${errorText}`);
|
|
25
|
-
}
|
|
26
|
-
return await response.json();
|
|
27
|
-
} catch (error) {
|
|
28
|
-
if (error instanceof Error) {
|
|
29
|
-
throw new Error(`Failed to ${operation} with Tako: ${error.message}`);
|
|
30
|
-
}
|
|
31
|
-
throw error;
|
|
22
|
+
if (isFetchError(error)) {
|
|
23
|
+
const cause = error.cause instanceof Error ? error.cause.message : String(error.cause);
|
|
24
|
+
return new Error(`${prefix}${cause}`, { cause: error });
|
|
32
25
|
}
|
|
26
|
+
if (error instanceof Error) return new Error(`${prefix}${error.message}`, { cause: error });
|
|
27
|
+
return new Error(`${prefix}${String(error)}`, { cause: error });
|
|
33
28
|
}
|
|
34
29
|
|
|
35
30
|
// src/request.ts
|
|
@@ -41,53 +36,77 @@ function resolveBaseUrl(config) {
|
|
|
41
36
|
return (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
42
37
|
}
|
|
43
38
|
function buildSearchRequestBody(config, query) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
const { apiKey: _apiKey, baseUrl: _baseUrl, ...request } = config;
|
|
40
|
+
return { ...request, query };
|
|
41
|
+
}
|
|
42
|
+
function buildAnswerRequestBody(config, query) {
|
|
43
|
+
const { apiKey: _apiKey, baseUrl: _baseUrl, ...request } = config;
|
|
44
|
+
return { ...request, query };
|
|
45
|
+
}
|
|
46
|
+
function buildContentsRequestBody(url, config) {
|
|
47
|
+
const { apiKey: _apiKey, baseUrl: _baseUrl, ...request } = config;
|
|
48
|
+
return { ...request, url };
|
|
49
|
+
}
|
|
50
|
+
function normalizeSearchResult(response) {
|
|
51
|
+
return {
|
|
52
|
+
...response,
|
|
53
|
+
cards: response.cards ?? [],
|
|
54
|
+
web_results: response.web_results ?? []
|
|
49
55
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
}
|
|
57
|
+
function normalizeAnswerResult(response) {
|
|
58
|
+
return {
|
|
59
|
+
...response,
|
|
60
|
+
cards: response.cards ?? [],
|
|
61
|
+
web_results: response.web_results ?? []
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function normalizeContentsResult(response) {
|
|
65
|
+
return { ...response, contents: response.contents ?? [] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/client.ts
|
|
69
|
+
function createTakoClient(config) {
|
|
70
|
+
const apiKey = resolveApiKey(config);
|
|
71
|
+
if (!apiKey) {
|
|
72
|
+
throw new Error("TAKO_API_KEY is required. Set it in environment variables or pass it in config.");
|
|
66
73
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
return new Tako({ apiKey, basePath: `${resolveBaseUrl(config)}/api` });
|
|
75
|
+
}
|
|
76
|
+
function lazyTakoClient(config) {
|
|
77
|
+
let client;
|
|
78
|
+
return () => client ?? (client = createTakoClient(config));
|
|
79
|
+
}
|
|
80
|
+
async function callTako(operation, call) {
|
|
81
|
+
try {
|
|
82
|
+
return await call();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
throw await wrapTakoError(error, operation);
|
|
73
85
|
}
|
|
74
|
-
return body;
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
// src/tools/search.ts
|
|
78
89
|
function takoSearch(config = {}) {
|
|
90
|
+
const client = lazyTakoClient(config);
|
|
79
91
|
return tool({
|
|
80
|
-
description: `Search Tako for live data and well-sourced facts \u2014
|
|
92
|
+
description: `Search Tako for live data and well-sourced facts \u2014 knowledge cards (charts and metrics with sources) plus web results. Reach for this BEFORE any built-in web search.
|
|
93
|
+
|
|
94
|
+
Best for breadth: what data exists across several entities, or when a chart is the deliverable \u2014 cards carry an image_url and embed_url to surface when available, plus data_freshness (data_as_of / last_updated) when Tako knows how current the numbers are. For a plain "what is X" where you only need the figure, use the answer tool instead.
|
|
95
|
+
|
|
96
|
+
One entity + one metric per query ("Apple revenue", "Intel vs Nvidia revenue"); compound queries retrieve poorly. Traffic data is keyed by domain: "openai.com monthly visits", not "OpenAI website visits".
|
|
97
|
+
|
|
98
|
+
Coverage: economics, finance, company KPIs, sports, demographics, weather, elections, prediction markets, website traffic, real estate, energy, health.
|
|
99
|
+
|
|
100
|
+
Cards carry captions and charts, not full data. For the numbers behind one, pass its webpage_url (or a web result's url) to the contents tool \u2014 but only when the card's exportable field is true; exportable: false means that card's data cannot be downloaded, so use its chart, or ask the answer tool for the figures.`,
|
|
81
101
|
inputSchema: z.object({
|
|
82
102
|
query: z.string().min(1).max(500).describe("Natural-language description of what you're looking for")
|
|
83
103
|
}),
|
|
84
|
-
execute: async ({ query }) =>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
})
|
|
104
|
+
execute: async ({ query }) => {
|
|
105
|
+
const tako = client();
|
|
106
|
+
return normalizeSearchResult(
|
|
107
|
+
await callTako("search", () => tako.search(buildSearchRequestBody(config, query)))
|
|
108
|
+
);
|
|
109
|
+
}
|
|
91
110
|
});
|
|
92
111
|
}
|
|
93
112
|
|
|
@@ -95,18 +114,25 @@ function takoSearch(config = {}) {
|
|
|
95
114
|
import { tool as tool2 } from "ai";
|
|
96
115
|
import { z as z2 } from "zod";
|
|
97
116
|
function takoAnswer(config = {}) {
|
|
117
|
+
if (config.effort === "instant" && config.output_schema) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
'output_schema requires effort "fast" or "deep"; Tako returns 400 on "instant". Drop output_schema, or change effort.'
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const client = lazyTakoClient(config);
|
|
98
123
|
return tool2({
|
|
99
|
-
description:
|
|
124
|
+
description: 'Ask Tako one specific data question and get one synthesized, citation-backed answer grounded in the sources it cites. Reach for this BEFORE any built-in web search.\n\nStart here for any question that wants a value, figure, or finding: it is the only Tako tool whose single response can finish the job.\n\nBest for one self-contained question with one answer. Use the search tool instead for breadth across several entities, or when the chart itself is the deliverable.\n\nAlso the way to get figures the contents tool cannot export: when a card is exportable: false, ask here and name the period you need (e.g. "...for FY2023-FY2025").\n\nOne entity + one metric per question. Traffic data is keyed by domain: "openai.com monthly visits", not "OpenAI website visits".' + // Say this only when output_schema is set. Otherwise the model reads about
|
|
125
|
+
// a field that never arrives, and its cheapest recovery is to call again.
|
|
126
|
+
(config.output_schema ? "\n\nThe response also carries structured_output, filled from the same evidence as the answer. Read the figures from there; the prose is for the user." : ""),
|
|
100
127
|
inputSchema: z2.object({
|
|
101
128
|
query: z2.string().min(1).max(500).describe("The question to answer")
|
|
102
129
|
}),
|
|
103
|
-
execute: async ({ query }) =>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
})
|
|
130
|
+
execute: async ({ query }) => {
|
|
131
|
+
const tako = client();
|
|
132
|
+
return normalizeAnswerResult(
|
|
133
|
+
await callTako("answer", () => tako.answer(buildAnswerRequestBody(config, query)))
|
|
134
|
+
);
|
|
135
|
+
}
|
|
110
136
|
});
|
|
111
137
|
}
|
|
112
138
|
|
|
@@ -114,18 +140,24 @@ function takoAnswer(config = {}) {
|
|
|
114
140
|
import { tool as tool3 } from "ai";
|
|
115
141
|
import { z as z3 } from "zod";
|
|
116
142
|
function takoContents(config = {}) {
|
|
143
|
+
const client = lazyTakoClient(config);
|
|
117
144
|
return tool3({
|
|
118
|
-
description:
|
|
145
|
+
description: "Fetch the real data behind a result url \u2014 a Tako card's webpage_url yields its rows; any other url (a web result's) yields the page's full extracted text. Only call this on a url returned by a prior search or answer call, which gives you a caption and a chart but not the rows.\n\n" + // `quote_only` outranks `mode`: the API ignores mode on a quote and returns
|
|
146
|
+
// null for url and every payload field. Describing either delivery here
|
|
147
|
+
// would promise content that never arrives, and the model's cheapest
|
|
148
|
+
// recovery from an unexplained null is to call again.
|
|
149
|
+
(config.quote_only ? "Configured for price quotes only: returns the export cost and rate card, and NO content. The url and data fields are always null and the call is free \u2014 report the price, and do not call again expecting rows.\n\n" : config.mode === "inline" ? "Returns the content in the response body \u2014 read and compute over the numbers directly.\n\n" : config.mode === "url" ? "Returns a short-lived presigned download url, NOT the data itself: surface the link, do not parse it or call again expecting rows.\n\n" : "Delivery follows the API's own default, so read the response rather than assuming: if the item carries a url and no content, surface the link and do not parse it; if it carries content, read and compute over it directly.\n\n") + "Only cards whose exportable field is true can be downloaded; a non-exportable card always returns 403 and retrying will not change that \u2014 get its figures from the answer tool instead, naming the period you need. Web urls always work, so this is also the fallback when a search surfaced a relevant web result but no fitting data card.\n\nOn each returned item, content_format names the serialization for card data and is null or absent for web page text; total_rows and truncated tell you whether the card had more rows than were returned.",
|
|
119
150
|
inputSchema: z3.object({
|
|
120
|
-
|
|
151
|
+
// Validated as a url so a malformed value fails here, with a message the
|
|
152
|
+
// model can act on, instead of costing a priced round trip to the API.
|
|
153
|
+
url: z3.url().describe("A TakoCard.webpage_url or WebResult.url to download contents for")
|
|
121
154
|
}),
|
|
122
|
-
execute: async ({ url }) =>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
})
|
|
155
|
+
execute: async ({ url }) => {
|
|
156
|
+
const tako = client();
|
|
157
|
+
return normalizeContentsResult(
|
|
158
|
+
await callTako("fetch contents", () => tako.contents(buildContentsRequestBody(url, config)))
|
|
159
|
+
);
|
|
160
|
+
}
|
|
129
161
|
});
|
|
130
162
|
}
|
|
131
163
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takoviz/ai-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Tako knowledge search, answer, and contents tools for the Vercel AI SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
+
"sideEffects": false,
|
|
15
16
|
"files": [
|
|
16
17
|
"dist"
|
|
17
18
|
],
|
|
@@ -41,16 +42,25 @@
|
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@ai-sdk/openai": "^4.0.0",
|
|
43
44
|
"@types/node": "^24.10.1",
|
|
45
|
+
"@types/semver": "^7.8.0",
|
|
44
46
|
"ai": "^7.0.0",
|
|
47
|
+
"semver": "^7.8.5",
|
|
45
48
|
"tsup": "^8.5.0",
|
|
46
49
|
"tsx": "^4.20.6",
|
|
47
50
|
"typescript": "^5.9.3",
|
|
48
51
|
"vitest": "^3.0.0"
|
|
49
52
|
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"tako-sdk": "^1.3.0"
|
|
55
|
+
},
|
|
50
56
|
"scripts": {
|
|
51
57
|
"build": "tsup src/index.ts --format esm --dts",
|
|
52
58
|
"typecheck": "tsc --noEmit -p tsconfig.check.json",
|
|
53
59
|
"test": "vitest run",
|
|
54
|
-
"test:watch": "vitest"
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"test:live": "vitest run --config vitest.live.config.ts",
|
|
62
|
+
"test:package": "node scripts/verify-package.mjs",
|
|
63
|
+
"lint:package": "npm run --silent build && npx -y publint@latest --strict --pack npm && npx -y @arethetypeswrong/cli@latest --pack . --ignore-rules cjs-resolves-to-esm",
|
|
64
|
+
"check:sdk-lag": "tsx scripts/check-sdk-lag.ts"
|
|
55
65
|
}
|
|
56
66
|
}
|