@traceai/cohere 0.1.0 → 0.1.1
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 +78 -369
- package/dist/esm/attributes.d.ts +11 -0
- package/dist/esm/attributes.d.ts.map +1 -0
- package/dist/esm/attributes.js +240 -0
- package/dist/esm/attributes.js.map +1 -0
- package/dist/esm/instrumentation.d.ts +11 -20
- package/dist/esm/instrumentation.d.ts.map +1 -1
- package/dist/esm/instrumentation.js +175 -344
- package/dist/esm/instrumentation.js.map +1 -1
- package/dist/esm/stream.d.ts +6 -0
- package/dist/esm/stream.d.ts.map +1 -0
- package/dist/esm/stream.js +215 -0
- package/dist/esm/stream.js.map +1 -0
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/esnext/attributes.d.ts +11 -0
- package/dist/esnext/attributes.d.ts.map +1 -0
- package/dist/esnext/attributes.js +248 -0
- package/dist/esnext/attributes.js.map +1 -0
- package/dist/esnext/instrumentation.d.ts +11 -20
- package/dist/esnext/instrumentation.d.ts.map +1 -1
- package/dist/esnext/instrumentation.js +178 -331
- package/dist/esnext/instrumentation.js.map +1 -1
- package/dist/esnext/stream.d.ts +6 -0
- package/dist/esnext/stream.d.ts.map +1 -0
- package/dist/esnext/stream.js +203 -0
- package/dist/esnext/stream.js.map +1 -0
- package/dist/esnext/tsconfig.esnext.tsbuildinfo +1 -1
- package/dist/esnext/version.d.ts +1 -1
- package/dist/esnext/version.js +1 -1
- package/dist/esnext/version.js.map +1 -1
- package/dist/src/__tests__/attributes.test.d.ts +2 -0
- package/dist/src/__tests__/attributes.test.d.ts.map +1 -0
- package/dist/src/__tests__/attributes.test.js +50 -0
- package/dist/src/__tests__/attributes.test.js.map +1 -0
- package/dist/src/__tests__/auto.test.d.ts +2 -0
- package/dist/src/__tests__/auto.test.d.ts.map +1 -0
- package/dist/src/__tests__/auto.test.js +70 -0
- package/dist/src/__tests__/auto.test.js.map +1 -0
- package/dist/src/__tests__/instrumentation.test.js +1 -1
- package/dist/src/__tests__/instrumentation.test.js.map +1 -1
- package/dist/src/__tests__/lifecycle.test.d.ts +2 -0
- package/dist/src/__tests__/lifecycle.test.d.ts.map +1 -0
- package/dist/src/__tests__/lifecycle.test.js +232 -0
- package/dist/src/__tests__/lifecycle.test.js.map +1 -0
- package/dist/src/__tests__/metadata.test.d.ts +2 -0
- package/dist/src/__tests__/metadata.test.d.ts.map +1 -0
- package/dist/src/__tests__/metadata.test.js +320 -0
- package/dist/src/__tests__/metadata.test.js.map +1 -0
- package/dist/src/__tests__/sdk.test.d.ts +2 -0
- package/dist/src/__tests__/sdk.test.d.ts.map +1 -0
- package/dist/src/__tests__/sdk.test.js +883 -0
- package/dist/src/__tests__/sdk.test.js.map +1 -0
- package/dist/src/__tests__/timing.test.d.ts +2 -0
- package/dist/src/__tests__/timing.test.d.ts.map +1 -0
- package/dist/src/__tests__/timing.test.js +158 -0
- package/dist/src/__tests__/timing.test.js.map +1 -0
- package/dist/src/attributes.d.ts +11 -0
- package/dist/src/attributes.d.ts.map +1 -0
- package/dist/src/attributes.js +257 -0
- package/dist/src/attributes.js.map +1 -0
- package/dist/src/instrumentation.d.ts +11 -20
- package/dist/src/instrumentation.d.ts.map +1 -1
- package/dist/src/instrumentation.js +176 -329
- package/dist/src/instrumentation.js.map +1 -1
- package/dist/src/stream.d.ts +6 -0
- package/dist/src/stream.d.ts.map +1 -0
- package/dist/src/stream.js +215 -0
- package/dist/src/stream.js.map +1 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -11
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
const node_http_1 = require("node:http");
|
|
20
|
+
const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
|
|
21
|
+
const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
|
|
22
|
+
const fi_semantic_conventions_1 = require("@traceai/fi-semantic-conventions");
|
|
23
|
+
const instrumentation_1 = require("../instrumentation");
|
|
24
|
+
const api_1 = require("@opentelemetry/api");
|
|
25
|
+
const core_1 = require("@opentelemetry/core");
|
|
26
|
+
const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
|
|
27
|
+
// Real SDK serializers and transports, with an offline localhost API double.
|
|
28
|
+
const sdk8 = require("cohere-ai");
|
|
29
|
+
const sdk7 = require("cohere-ai-v7");
|
|
30
|
+
const exporter = new sdk_trace_base_1.InMemorySpanExporter();
|
|
31
|
+
const provider = new sdk_trace_node_1.NodeTracerProvider({ spanProcessors: [new sdk_trace_base_1.SimpleSpanProcessor(exporter)] });
|
|
32
|
+
let server;
|
|
33
|
+
let url;
|
|
34
|
+
let response;
|
|
35
|
+
let contentType = "application/json";
|
|
36
|
+
let status = 200;
|
|
37
|
+
let received = [];
|
|
38
|
+
let wire = [];
|
|
39
|
+
let instrumentation;
|
|
40
|
+
const v1Response = { text: "hello", generation_id: "generation", meta: { tokens: { input_tokens: 5, output_tokens: 2 } } };
|
|
41
|
+
const v2Response = { id: "generation", finish_reason: "COMPLETE", message: { role: "assistant", content: [{ type: "text", text: "hello" }] }, usage: { tokens: { input_tokens: 5, output_tokens: 2 } } };
|
|
42
|
+
beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
43
|
+
provider.register();
|
|
44
|
+
server = (0, node_http_1.createServer)((request, reply) => {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
request.on("data", (chunk) => chunks.push(chunk));
|
|
47
|
+
request.on("end", () => {
|
|
48
|
+
const body = JSON.parse(Buffer.concat(chunks).toString() || "{}");
|
|
49
|
+
if (request.url === "/v1/traces") {
|
|
50
|
+
wire.push(body);
|
|
51
|
+
reply.setHeader("Content-Type", "application/json");
|
|
52
|
+
reply.end("{}");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
received.push({ path: request.url, body, headers: request.headers });
|
|
56
|
+
reply.statusCode = status;
|
|
57
|
+
reply.setHeader("Content-Type", contentType);
|
|
58
|
+
reply.end(typeof response === "string" ? response : JSON.stringify(response));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
yield new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
62
|
+
url = `http://127.0.0.1:${server.address().port}`;
|
|
63
|
+
}));
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
exporter.reset();
|
|
66
|
+
wire = [];
|
|
67
|
+
received = [];
|
|
68
|
+
status = 200;
|
|
69
|
+
contentType = "application/json";
|
|
70
|
+
instrumentation = new instrumentation_1.CohereInstrumentation();
|
|
71
|
+
instrumentation.setTracerProvider(provider);
|
|
72
|
+
});
|
|
73
|
+
afterEach(() => instrumentation.disable());
|
|
74
|
+
afterAll(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
75
|
+
yield new Promise((resolve) => server.close(() => resolve()));
|
|
76
|
+
yield provider.shutdown();
|
|
77
|
+
}));
|
|
78
|
+
it("traces CohereClientV2's generated v2 client", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
79
|
+
instrumentation.manuallyInstrument(sdk8);
|
|
80
|
+
response = v2Response;
|
|
81
|
+
const client = new sdk8.CohereClientV2({ token: "offline-placeholder", environment: url });
|
|
82
|
+
const result = yield client.chat({ model: "command-test", messages: [{ role: "user", content: "question" }] });
|
|
83
|
+
expect(result.message.content[0].text).toBe("hello");
|
|
84
|
+
expect(exporter.getFinishedSpans()).toHaveLength(1);
|
|
85
|
+
expect(exporter.getFinishedSpans()[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_PROMPT]).toBe(5);
|
|
86
|
+
}));
|
|
87
|
+
it.each([["7.0.0", sdk7], ["8.3.0", sdk8]])("reads the real %s SDK camelCase chat history", (_version, sdk) => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
instrumentation.manuallyInstrument(sdk);
|
|
89
|
+
response = v1Response;
|
|
90
|
+
const client = new sdk.CohereClient({ token: "offline-placeholder", environment: url });
|
|
91
|
+
yield client.chat({ model: "command-test", message: "question", chatHistory: [{ role: "USER", message: "earlier" }] });
|
|
92
|
+
expect(exporter.getFinishedSpans()).toHaveLength(1);
|
|
93
|
+
expect(exporter.getFinishedSpans()[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_INPUT_MESSAGES]).toContain("earlier");
|
|
94
|
+
}));
|
|
95
|
+
function clientFor(sdk, v2 = false) {
|
|
96
|
+
instrumentation.manuallyInstrument(sdk);
|
|
97
|
+
return new (v2 ? sdk.CohereClientV2 : sdk.CohereClient)({ token: "offline-placeholder", environment: url });
|
|
98
|
+
}
|
|
99
|
+
const request = { model: "command-test", message: "question" };
|
|
100
|
+
const requestV2 = { model: "command-test", messages: [{ role: "user", content: "question" }] };
|
|
101
|
+
function streamResponse(v2) {
|
|
102
|
+
const events = v2 ? [
|
|
103
|
+
{ type: "content-delta", index: 0, delta: { message: { content: { text: "hel" } } } },
|
|
104
|
+
{ type: "content-delta", index: 0, delta: { message: { content: { text: "lo" } } } },
|
|
105
|
+
{ type: "tool-call-start", index: 0, delta: { message: { tool_calls: { id: "call-1", type: "function", function: { name: "lookup", arguments: "{\"key\":" } } } } },
|
|
106
|
+
{ type: "tool-call-delta", index: 0, delta: { message: { tool_calls: { function: { arguments: "\"value\"}" } } } } },
|
|
107
|
+
{ type: "message-end", delta: { finish_reason: "COMPLETE", usage: { tokens: { input_tokens: 5, output_tokens: 2 } } } },
|
|
108
|
+
] : [
|
|
109
|
+
{ event_type: "text-generation", text: "hel" },
|
|
110
|
+
{ event_type: "text-generation", text: "lo" },
|
|
111
|
+
{ event_type: "stream-end", finish_reason: "COMPLETE", response: v1Response },
|
|
112
|
+
];
|
|
113
|
+
contentType = v2 ? "text/event-stream" : "application/x-ndjson";
|
|
114
|
+
response = events.map((event) => v2 ? `data: ${JSON.stringify(event)}\n\n` : `${JSON.stringify(event)}\n`).join("");
|
|
115
|
+
return events;
|
|
116
|
+
}
|
|
117
|
+
it.each([false, true])("traces real v2=%s SDK streaming through terminal usage and preserves helpers", (v2) => __awaiter(void 0, void 0, void 0, function* () {
|
|
118
|
+
var _a, e_1, _b, _c;
|
|
119
|
+
const client = clientFor(sdk8, v2);
|
|
120
|
+
const expected = streamResponse(v2);
|
|
121
|
+
const pending = client.chatStream(v2 ? requestV2 : request);
|
|
122
|
+
expect(typeof pending.withRawResponse).toBe("function");
|
|
123
|
+
const { data: stream, rawResponse } = yield pending.withRawResponse();
|
|
124
|
+
expect(rawResponse.status).toBe(200);
|
|
125
|
+
expect(yield pending).toBe(stream);
|
|
126
|
+
expect(stream.controller).toBeInstanceOf(AbortController);
|
|
127
|
+
expect(exporter.getFinishedSpans()).toHaveLength(0);
|
|
128
|
+
const events = [];
|
|
129
|
+
try {
|
|
130
|
+
for (var _d = true, stream_1 = __asyncValues(stream), stream_1_1; stream_1_1 = yield stream_1.next(), _a = stream_1_1.done, !_a; _d = true) {
|
|
131
|
+
_c = stream_1_1.value;
|
|
132
|
+
_d = false;
|
|
133
|
+
const event = _c;
|
|
134
|
+
events.push(event);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
138
|
+
finally {
|
|
139
|
+
try {
|
|
140
|
+
if (!_d && !_a && (_b = stream_1.return)) yield _b.call(stream_1);
|
|
141
|
+
}
|
|
142
|
+
finally { if (e_1) throw e_1.error; }
|
|
143
|
+
}
|
|
144
|
+
expect(events).toHaveLength(expected.length);
|
|
145
|
+
const spans = exporter.getFinishedSpans();
|
|
146
|
+
expect(spans).toHaveLength(1);
|
|
147
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBe("hello");
|
|
148
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_PROMPT]).toBe(5);
|
|
149
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_COMPLETION]).toBe(2);
|
|
150
|
+
expect(spans[0].status.code).toBe(api_1.SpanStatusCode.OK);
|
|
151
|
+
if (v2) {
|
|
152
|
+
const messages = JSON.parse(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES]);
|
|
153
|
+
expect(messages[0].tool_calls[0]).toEqual({ id: "call-1", type: "function", function: { name: "lookup", arguments: '{"key":"value"}' } });
|
|
154
|
+
}
|
|
155
|
+
}));
|
|
156
|
+
it.each([false, true])("closes a real v2=%s SDK stream when the consumer breaks early", (v2) => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
|
+
var _a, e_2, _b, _c;
|
|
158
|
+
const client = clientFor(sdk8, v2);
|
|
159
|
+
streamResponse(v2);
|
|
160
|
+
const stream = yield client.chatStream(v2 ? requestV2 : request);
|
|
161
|
+
try {
|
|
162
|
+
for (var _d = true, stream_2 = __asyncValues(stream), stream_2_1; stream_2_1 = yield stream_2.next(), _a = stream_2_1.done, !_a; _d = true) {
|
|
163
|
+
_c = stream_2_1.value;
|
|
164
|
+
_d = false;
|
|
165
|
+
const _event = _c;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
170
|
+
finally {
|
|
171
|
+
try {
|
|
172
|
+
if (!_d && !_a && (_b = stream_2.return)) yield _b.call(stream_2);
|
|
173
|
+
}
|
|
174
|
+
finally { if (e_2) throw e_2.error; }
|
|
175
|
+
}
|
|
176
|
+
expect(exporter.getFinishedSpans()).toHaveLength(1);
|
|
177
|
+
expect(exporter.getFinishedSpans()[0].attributes["cohere.stream.cancelled"]).toBe(true);
|
|
178
|
+
expect(exporter.getFinishedSpans()[0].attributes[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBe("hel");
|
|
179
|
+
expect(exporter.getFinishedSpans()[0].status.code).toBe(api_1.SpanStatusCode.UNSET);
|
|
180
|
+
}));
|
|
181
|
+
it("finalizes on abort before iteration, without consuming events", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
182
|
+
const client = clientFor(sdk8, true);
|
|
183
|
+
streamResponse(true);
|
|
184
|
+
const stream = yield client.chatStream(requestV2);
|
|
185
|
+
stream.controller.abort();
|
|
186
|
+
expect(exporter.getFinishedSpans()).toHaveLength(1);
|
|
187
|
+
expect(exporter.getFinishedSpans()[0].attributes["cohere.stream.cancelled"]).toBe(true);
|
|
188
|
+
}));
|
|
189
|
+
it("finalizes on return before first next", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
190
|
+
const client = clientFor(sdk8, true);
|
|
191
|
+
streamResponse(true);
|
|
192
|
+
const stream = yield client.chatStream(requestV2);
|
|
193
|
+
yield stream[Symbol.asyncIterator]().return();
|
|
194
|
+
expect(exporter.getFinishedSpans()).toHaveLength(1);
|
|
195
|
+
}));
|
|
196
|
+
it("records SDK stream parser errors and preserves the rejection", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
197
|
+
const client = clientFor(sdk8, true);
|
|
198
|
+
contentType = "text/event-stream";
|
|
199
|
+
response = "data: not-json\n\n";
|
|
200
|
+
const stream = yield client.chatStream(requestV2);
|
|
201
|
+
yield expect(stream[Symbol.asyncIterator]().next()).rejects.toBeInstanceOf(Error);
|
|
202
|
+
expect(exporter.getFinishedSpans()).toHaveLength(1);
|
|
203
|
+
expect(exporter.getFinishedSpans()[0].status.code).toBe(api_1.SpanStatusCode.ERROR);
|
|
204
|
+
}));
|
|
205
|
+
it.each([["7.0.0", sdk7], ["8.3.0", sdk8]])("preserves real %s SDK embeddings and reranker types", (_version, sdk) => __awaiter(void 0, void 0, void 0, function* () {
|
|
206
|
+
const client = clientFor(sdk);
|
|
207
|
+
response = { id: "embedding", response_type: "embeddings_floats", texts: ["document"], embeddings: [[0.1, 0.2]], meta: { billed_units: { input_tokens: 3 } } };
|
|
208
|
+
yield client.embed({ texts: ["document"], model: "embed-test", inputType: "search_document" });
|
|
209
|
+
response = { id: "rerank", results: [{ index: 0, relevance_score: 0.9, document: { text: "document" } }] };
|
|
210
|
+
const result = yield client.rerank({ model: "rerank-test", query: "query", documents: [{ text: "document" }], topN: 1 });
|
|
211
|
+
expect(result.results[0].relevanceScore).toBe(0.9);
|
|
212
|
+
const spans = exporter.getFinishedSpans();
|
|
213
|
+
expect(spans).toHaveLength(2);
|
|
214
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.FI_SPAN_KIND]).toBe("EMBEDDING");
|
|
215
|
+
expect(spans[0].attributes[`${fi_semantic_conventions_1.SemanticConventions.EMBEDDING_EMBEDDINGS}.0.${fi_semantic_conventions_1.SemanticConventions.EMBEDDING_VECTOR}`]).toEqual([0.1, 0.2]);
|
|
216
|
+
expect(spans[1].attributes[fi_semantic_conventions_1.SemanticConventions.FI_SPAN_KIND]).toBe("RERANKER");
|
|
217
|
+
expect(spans[1].attributes["cohere.rerank.results.0.relevance_score"]).toBe(0.9);
|
|
218
|
+
expect(spans[1].attributes["cohere.rerank.top_n"]).toBe(1);
|
|
219
|
+
expect(received[1].body.top_n).toBe(1);
|
|
220
|
+
}));
|
|
221
|
+
it("instruments v2 embeddings, rerank and client.v2 without duplicate spans", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
222
|
+
const client = clientFor(sdk8, true);
|
|
223
|
+
response = { id: "embedding", texts: ["document"], embeddings: { float: [[0.1, 0.2]] }, meta: { billed_units: { input_tokens: 3 } } };
|
|
224
|
+
yield client.embed({ texts: ["document"], model: "embed-test", inputType: "search_document", embeddingTypes: ["float"] });
|
|
225
|
+
response = { id: "rerank", results: [{ index: 0, relevance_score: 0.9 }] };
|
|
226
|
+
yield client.rerank({ model: "rerank-test", query: "query", documents: ["document"] });
|
|
227
|
+
response = v2Response;
|
|
228
|
+
const v1 = new sdk8.CohereClient({ token: "offline-placeholder", environment: url });
|
|
229
|
+
const pending = v1.v2.chat(requestV2, { headers: { "X-Test-Forwarded": "yes" } });
|
|
230
|
+
const raw = yield pending.withRawResponse();
|
|
231
|
+
expect(raw.data).toBe(yield pending);
|
|
232
|
+
expect(raw.rawResponse.status).toBe(200);
|
|
233
|
+
expect(received[2].headers).toMatchObject({ "x-test-forwarded": "yes" });
|
|
234
|
+
const spans = exporter.getFinishedSpans();
|
|
235
|
+
expect(spans).toHaveLength(3);
|
|
236
|
+
expect(spans.map((span) => span.attributes[fi_semantic_conventions_1.SemanticConventions.FI_SPAN_KIND])).toEqual(["EMBEDDING", "RERANKER", "LLM"]);
|
|
237
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_PROMPT]).toBe(3);
|
|
238
|
+
}));
|
|
239
|
+
it("does not invent a model name when the caller leaves it to Cohere", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
240
|
+
const client = clientFor(sdk7);
|
|
241
|
+
response = v1Response;
|
|
242
|
+
yield client.chat({ message: "question" });
|
|
243
|
+
expect(exporter.getFinishedSpans()[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_MODEL_NAME]).toBeUndefined();
|
|
244
|
+
}));
|
|
245
|
+
it("supports multiple SDK copies, idempotent manual patching and enable/disable", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
246
|
+
const client7 = clientFor(sdk7);
|
|
247
|
+
const client8 = clientFor(sdk8, true);
|
|
248
|
+
instrumentation.manuallyInstrument(sdk8);
|
|
249
|
+
expect((0, instrumentation_1.isPatched)()).toBe(true);
|
|
250
|
+
response = v1Response;
|
|
251
|
+
yield client7.chat(request);
|
|
252
|
+
response = v2Response;
|
|
253
|
+
yield client8.chat(requestV2);
|
|
254
|
+
expect(exporter.getFinishedSpans()).toHaveLength(2);
|
|
255
|
+
instrumentation.disable();
|
|
256
|
+
expect((0, instrumentation_1.isPatched)()).toBe(false);
|
|
257
|
+
yield client8.chat(requestV2); // already-bound method must respect disable
|
|
258
|
+
instrumentation.enable();
|
|
259
|
+
yield client8.chat(requestV2);
|
|
260
|
+
const newClient8 = clientFor(sdk8, true);
|
|
261
|
+
yield newClient8.chat(requestV2);
|
|
262
|
+
expect(exporter.getFinishedSpans()).toHaveLength(4);
|
|
263
|
+
}));
|
|
264
|
+
it("does not let a second instrumentor unwrap another owner's methods", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
265
|
+
const client = clientFor(sdk8, true);
|
|
266
|
+
const other = new instrumentation_1.CohereInstrumentation();
|
|
267
|
+
other.setTracerProvider(provider);
|
|
268
|
+
other.manuallyInstrument(sdk8);
|
|
269
|
+
other.disable();
|
|
270
|
+
response = v2Response;
|
|
271
|
+
yield client.chat(requestV2);
|
|
272
|
+
expect(exporter.getFinishedSpans()).toHaveLength(1);
|
|
273
|
+
}));
|
|
274
|
+
it("honors suppression and propagates the parent across concurrent SDK calls", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
275
|
+
var _a;
|
|
276
|
+
const client = clientFor(sdk8, true);
|
|
277
|
+
response = v2Response;
|
|
278
|
+
yield api_1.context.with((0, core_1.suppressTracing)(api_1.context.active()), () => client.chat(requestV2));
|
|
279
|
+
expect(exporter.getFinishedSpans()).toHaveLength(0);
|
|
280
|
+
const parent = provider.getTracer("parent").startSpan("parent");
|
|
281
|
+
yield api_1.context.with(api_1.trace.setSpan(api_1.context.active(), parent), () => Promise.all([client.chat(requestV2), client.chat(requestV2)]));
|
|
282
|
+
parent.end();
|
|
283
|
+
const children = exporter.getFinishedSpans().filter((span) => span.name !== "parent");
|
|
284
|
+
expect(children).toHaveLength(2);
|
|
285
|
+
for (const child of children)
|
|
286
|
+
expect((_a = child.parentSpanContext) === null || _a === void 0 ? void 0 : _a.spanId).toBe(parent.spanContext().spanId);
|
|
287
|
+
expect(api_1.trace.getActiveSpan()).toBeUndefined();
|
|
288
|
+
}));
|
|
289
|
+
it.each(["normal", "raw", "stream"])("records real SDK HTTP errors using the %s path once", (mode) => __awaiter(void 0, void 0, void 0, function* () {
|
|
290
|
+
const client = clientFor(sdk8, true);
|
|
291
|
+
status = 400;
|
|
292
|
+
response = { message: "fixture-error" };
|
|
293
|
+
const pending = mode === "stream" ? client.chatStream(requestV2) : client.chat(requestV2);
|
|
294
|
+
yield expect(mode === "raw" ? pending.withRawResponse() : pending).rejects.toBeInstanceOf(sdk8.CohereError);
|
|
295
|
+
expect(exporter.getFinishedSpans()).toHaveLength(1);
|
|
296
|
+
expect(exporter.getFinishedSpans()[0].status.code).toBe(api_1.SpanStatusCode.ERROR);
|
|
297
|
+
}));
|
|
298
|
+
it.each(["content", "hidden", "vectors"])("exports correct shape and %s privacy over actual OTLP/HTTP", (mode) => __awaiter(void 0, void 0, void 0, function* () {
|
|
299
|
+
var _a, e_3, _b, _c;
|
|
300
|
+
instrumentation.disable();
|
|
301
|
+
const wireProvider = new sdk_trace_node_1.NodeTracerProvider({ spanProcessors: [new sdk_trace_base_1.SimpleSpanProcessor(new exporter_trace_otlp_http_1.OTLPTraceExporter({ url: `${url}/v1/traces` }))] });
|
|
302
|
+
instrumentation = new instrumentation_1.CohereInstrumentation({ traceConfig: mode === "hidden" ? { hideInputs: true, hideOutputs: true } : { hideEmbeddingVectors: mode === "vectors" } });
|
|
303
|
+
instrumentation.setTracerProvider(wireProvider);
|
|
304
|
+
try {
|
|
305
|
+
const client = clientFor(sdk8, true);
|
|
306
|
+
response = { id: "embedding", texts: ["secret-input"], embeddings: { float: [[0.123456789]] } };
|
|
307
|
+
yield client.embed({ texts: ["secret-input"], model: "embed-test", inputType: "search_document", embeddingTypes: ["float"] });
|
|
308
|
+
response = { id: "rerank", results: [{ index: 0, relevance_score: 0.9, document: { text: "secret-output" } }] };
|
|
309
|
+
yield client.rerank({ model: "rerank-test", query: "secret-input", documents: ["secret-input"], topN: 1 });
|
|
310
|
+
streamResponse(true);
|
|
311
|
+
response = String(response).replaceAll("hel", "secret-output");
|
|
312
|
+
const stream = yield client.chatStream(Object.assign(Object.assign({}, requestV2), { messages: [{ role: "user", content: "secret-input" }], tools: [{ type: "function", function: { name: "tool-secret-input", description: "secret-input", parameters: {} } }] }));
|
|
313
|
+
try {
|
|
314
|
+
for (var _d = true, stream_3 = __asyncValues(stream), stream_3_1; stream_3_1 = yield stream_3.next(), _a = stream_3_1.done, !_a; _d = true) {
|
|
315
|
+
_c = stream_3_1.value;
|
|
316
|
+
_d = false;
|
|
317
|
+
const _event = _c;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (e_3_1) { e_3 = { error: e_3_1 }; }
|
|
321
|
+
finally {
|
|
322
|
+
try {
|
|
323
|
+
if (!_d && !_a && (_b = stream_3.return)) yield _b.call(stream_3);
|
|
324
|
+
}
|
|
325
|
+
finally { if (e_3) throw e_3.error; }
|
|
326
|
+
}
|
|
327
|
+
status = 400;
|
|
328
|
+
response = { message: "secret-input secret-output" };
|
|
329
|
+
yield expect(client.chat(requestV2)).rejects.toBeInstanceOf(Error);
|
|
330
|
+
yield wireProvider.forceFlush();
|
|
331
|
+
const encoded = JSON.stringify(wire);
|
|
332
|
+
const spans = wire.flatMap((batch) => batch.resourceSpans.flatMap((resource) => resource.scopeSpans.flatMap((scope) => scope.spans)));
|
|
333
|
+
expect(spans).toHaveLength(4);
|
|
334
|
+
const attrs = (span) => Object.fromEntries(span.attributes.map((item) => [item.key, Object.values(item.value)[0]]));
|
|
335
|
+
expect(attrs(spans[0])[fi_semantic_conventions_1.SemanticConventions.FI_SPAN_KIND]).toBe("EMBEDDING");
|
|
336
|
+
expect(attrs(spans[1])[fi_semantic_conventions_1.SemanticConventions.FI_SPAN_KIND]).toBe("RERANKER");
|
|
337
|
+
expect(attrs(spans[2])[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_PROMPT]).toBe(5);
|
|
338
|
+
expect(spans[3].status.code).toBe(api_1.SpanStatusCode.ERROR);
|
|
339
|
+
if (mode === "hidden") {
|
|
340
|
+
expect(encoded).not.toContain("secret-input");
|
|
341
|
+
expect(encoded).not.toContain("secret-output");
|
|
342
|
+
expect(encoded).not.toContain("0.123456789");
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
expect(encoded).toContain("secret-input");
|
|
346
|
+
expect(encoded).toContain("secret-output");
|
|
347
|
+
}
|
|
348
|
+
if (mode === "vectors")
|
|
349
|
+
expect(encoded).not.toContain("0.123456789");
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
yield wireProvider.shutdown();
|
|
353
|
+
}
|
|
354
|
+
}));
|
|
355
|
+
it.each([[false, false], [false, true], [true, false], [true, true]])("marks premature EOF as incomplete (v2=%s, empty=%s) without changing SDK iteration", (v2, empty) => __awaiter(void 0, void 0, void 0, function* () {
|
|
356
|
+
var _a, e_4, _b, _c;
|
|
357
|
+
const client = clientFor(sdk8, v2);
|
|
358
|
+
const events = streamResponse(v2);
|
|
359
|
+
response = empty ? "" : (v2 ? `data: ${JSON.stringify(events[0])}\n\n` : `${JSON.stringify(events[0])}\n`);
|
|
360
|
+
const delivered = [];
|
|
361
|
+
try {
|
|
362
|
+
for (var _d = true, _e = __asyncValues(yield client.chatStream(v2 ? requestV2 : request)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
363
|
+
_c = _f.value;
|
|
364
|
+
_d = false;
|
|
365
|
+
const event = _c;
|
|
366
|
+
delivered.push(event);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
370
|
+
finally {
|
|
371
|
+
try {
|
|
372
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
373
|
+
}
|
|
374
|
+
finally { if (e_4) throw e_4.error; }
|
|
375
|
+
}
|
|
376
|
+
expect(delivered).toHaveLength(empty ? 0 : 1);
|
|
377
|
+
const spans = exporter.getFinishedSpans();
|
|
378
|
+
expect(spans).toHaveLength(1);
|
|
379
|
+
expect(spans[0].status.code).toBe(api_1.SpanStatusCode.ERROR);
|
|
380
|
+
expect(spans[0].status.message).toBe("Cohere stream ended without a terminal event");
|
|
381
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBe(empty ? "" : "hel");
|
|
382
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_PROMPT]).toBeUndefined();
|
|
383
|
+
}));
|
|
384
|
+
const privacyClients = [
|
|
385
|
+
["7.0.0 v1", sdk7, false], ["8.3.0 v1", sdk8, false], ["8.3.0 v2", sdk8, true],
|
|
386
|
+
];
|
|
387
|
+
describe.each(privacyClients)("%s response privacy", (_label, sdk, v2) => {
|
|
388
|
+
it.each(["hideInputs", "hideInputMessages", "hideInputText", "hideInputImages"])("removes response-side input echoes with %s but retains generated output on the OTLP wire", (flag) => __awaiter(void 0, void 0, void 0, function* () {
|
|
389
|
+
instrumentation.disable();
|
|
390
|
+
const exporter = new sdk_trace_base_1.InMemorySpanExporter();
|
|
391
|
+
const wireProvider = new sdk_trace_node_1.NodeTracerProvider({ spanProcessors: [
|
|
392
|
+
new sdk_trace_base_1.SimpleSpanProcessor(exporter),
|
|
393
|
+
new sdk_trace_base_1.SimpleSpanProcessor(new exporter_trace_otlp_http_1.OTLPTraceExporter({ url: `${url}/v1/traces` })),
|
|
394
|
+
] });
|
|
395
|
+
instrumentation = new instrumentation_1.CohereInstrumentation({ traceConfig: { [flag]: true } });
|
|
396
|
+
instrumentation.setTracerProvider(wireProvider);
|
|
397
|
+
try {
|
|
398
|
+
const client = clientFor(sdk, v2);
|
|
399
|
+
response = v2 ? Object.assign(Object.assign({}, v2Response), { message: { role: "assistant", content: [{ type: "text", text: "generated-answer" }],
|
|
400
|
+
citations: [{ start: 0, end: 6, text: "generated-answer", sources: [{ type: "document", id: "doc", document: { text: "private-input-marker" } }] }] } }) : Object.assign(Object.assign({}, v1Response), { text: "generated-answer", chat_history: [
|
|
401
|
+
{ role: "USER", message: "private-input-marker" },
|
|
402
|
+
{ role: "CHATBOT", message: "private-input-marker-previous-answer" },
|
|
403
|
+
{ role: "CHATBOT", message: "generated-answer" },
|
|
404
|
+
] });
|
|
405
|
+
const result = yield client.chat(v2 ? Object.assign(Object.assign({}, requestV2), { messages: [{ role: "user", content: "private-input-marker" }] }) : Object.assign(Object.assign({}, request), { message: "private-input-marker" }));
|
|
406
|
+
expect(v2 ? result.message.content[0].text : result.text).toBe("generated-answer");
|
|
407
|
+
// Sanitizing telemetry must not alter the SDK's response object.
|
|
408
|
+
expect(JSON.stringify(result)).toContain("private-input-marker");
|
|
409
|
+
response = { id: "embedding", response_type: "embeddings_floats", texts: ["private-input-marker"], embeddings: v2 ? { float: [[0.123456789]] } : [[0.123456789]] };
|
|
410
|
+
yield client.embed(Object.assign({ texts: ["private-input-marker"], model: "embed-test", inputType: "search_document" }, (v2 ? { embeddingTypes: ["float"] } : {})));
|
|
411
|
+
response = { id: "rerank", results: [{ index: 0, relevance_score: 0.9, document: { text: "private-input-marker" } }] };
|
|
412
|
+
yield client.rerank({ model: "rerank-test", query: "private-input-marker", documents: v2 ? ["private-input-marker"] : [{ text: "private-input-marker" }] });
|
|
413
|
+
yield wireProvider.forceFlush();
|
|
414
|
+
const spans = exporter.getFinishedSpans();
|
|
415
|
+
expect(spans).toHaveLength(3);
|
|
416
|
+
expect(JSON.stringify(spans.map(span => span.attributes))).not.toContain("private-input-marker");
|
|
417
|
+
expect(JSON.stringify(wire)).not.toContain("private-input-marker");
|
|
418
|
+
expect(JSON.stringify(wire)).toContain("generated-answer");
|
|
419
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBe("generated-answer");
|
|
420
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_PROMPT]).toBe(5);
|
|
421
|
+
expect(spans[1].attributes[`${fi_semantic_conventions_1.SemanticConventions.EMBEDDING_EMBEDDINGS}.0.${fi_semantic_conventions_1.SemanticConventions.EMBEDDING_VECTOR}`]).toEqual([0.123456789]);
|
|
422
|
+
expect(spans[2].attributes["cohere.rerank.results.0.relevance_score"]).toBe(0.9);
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
yield wireProvider.shutdown();
|
|
426
|
+
}
|
|
427
|
+
}));
|
|
428
|
+
it.each(["hideOutputs", "hideOutputMessages", "hideOutputText"])("keeps inputs visible when only %s is set", (flag) => __awaiter(void 0, void 0, void 0, function* () {
|
|
429
|
+
instrumentation.disable();
|
|
430
|
+
instrumentation = new instrumentation_1.CohereInstrumentation({ traceConfig: { [flag]: true } });
|
|
431
|
+
instrumentation.setTracerProvider(provider);
|
|
432
|
+
const client = clientFor(sdk, v2);
|
|
433
|
+
response = v2 ? v2Response : v1Response;
|
|
434
|
+
yield client.chat(v2 ? requestV2 : request);
|
|
435
|
+
const attrs = exporter.getFinishedSpans()[0].attributes;
|
|
436
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.INPUT_VALUE]).toContain("question");
|
|
437
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).toBeUndefined();
|
|
438
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBeUndefined();
|
|
439
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_PROMPT]).toBe(5);
|
|
440
|
+
}));
|
|
441
|
+
});
|
|
442
|
+
it("hides v1 terminal stream response echoes without losing generated text", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
443
|
+
var _a, e_5, _b, _c;
|
|
444
|
+
instrumentation.disable();
|
|
445
|
+
instrumentation = new instrumentation_1.CohereInstrumentation({ traceConfig: { hideInputs: true } });
|
|
446
|
+
instrumentation.setTracerProvider(provider);
|
|
447
|
+
const client = clientFor(sdk8);
|
|
448
|
+
contentType = "application/x-ndjson";
|
|
449
|
+
response = JSON.stringify({ event_type: "stream-end", finish_reason: "COMPLETE", response: Object.assign(Object.assign({}, v1Response), { chat_history: [{ role: "USER", message: "private-input-marker" }] }) }) + "\n";
|
|
450
|
+
try {
|
|
451
|
+
for (var _d = true, _e = __asyncValues(yield client.chatStream(Object.assign(Object.assign({}, request), { message: "private-input-marker" }))), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
452
|
+
_c = _f.value;
|
|
453
|
+
_d = false;
|
|
454
|
+
const _event = _c;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
458
|
+
finally {
|
|
459
|
+
try {
|
|
460
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
461
|
+
}
|
|
462
|
+
finally { if (e_5) throw e_5.error; }
|
|
463
|
+
}
|
|
464
|
+
const spans = exporter.getFinishedSpans();
|
|
465
|
+
expect(spans).toHaveLength(1);
|
|
466
|
+
expect(JSON.stringify(spans[0].attributes)).not.toContain("private-input-marker");
|
|
467
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBe("hello");
|
|
468
|
+
expect(spans[0].status.code).toBe(api_1.SpanStatusCode.OK);
|
|
469
|
+
}));
|
|
470
|
+
// Connector authentication is not user content and must never enter telemetry.
|
|
471
|
+
describe.each(["capture", "hideOutputs", "hideInputs"])("8.3.0 connector credentials (%s)", (mode) => {
|
|
472
|
+
it.each([false, true])("keeps credentials on the vendor request only (stream=%s)", (streaming) => __awaiter(void 0, void 0, void 0, function* () {
|
|
473
|
+
var _a, e_6, _b, _c;
|
|
474
|
+
instrumentation.disable();
|
|
475
|
+
const captured = new sdk_trace_base_1.InMemorySpanExporter();
|
|
476
|
+
const wireProvider = new sdk_trace_node_1.NodeTracerProvider({ spanProcessors: [
|
|
477
|
+
new sdk_trace_base_1.SimpleSpanProcessor(captured),
|
|
478
|
+
new sdk_trace_base_1.SimpleSpanProcessor(new exporter_trace_otlp_http_1.OTLPTraceExporter({ url: `${url}/v1/traces` })),
|
|
479
|
+
] });
|
|
480
|
+
instrumentation = new instrumentation_1.CohereInstrumentation({ traceConfig: mode === "capture" ? {} : { [mode]: true } });
|
|
481
|
+
instrumentation.setTracerProvider(wireProvider);
|
|
482
|
+
try {
|
|
483
|
+
const client = clientFor(sdk8);
|
|
484
|
+
const connector = Object.freeze({ id: "fixture-connector", userAccessToken: "synthetic-connector-credential", continueOnFailure: true, options: { site: "example.test" } });
|
|
485
|
+
const input = Object.freeze(Object.assign(Object.assign({}, request), { connectors: Object.freeze([connector]) }));
|
|
486
|
+
if (streaming) {
|
|
487
|
+
streamResponse(false);
|
|
488
|
+
try {
|
|
489
|
+
for (var _d = true, _e = __asyncValues(yield client.chatStream(input)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
490
|
+
_c = _f.value;
|
|
491
|
+
_d = false;
|
|
492
|
+
const _event = _c;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
496
|
+
finally {
|
|
497
|
+
try {
|
|
498
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
499
|
+
}
|
|
500
|
+
finally { if (e_6) throw e_6.error; }
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
response = v1Response;
|
|
505
|
+
yield client.chat(input);
|
|
506
|
+
}
|
|
507
|
+
yield wireProvider.forceFlush();
|
|
508
|
+
expect(received).toHaveLength(1);
|
|
509
|
+
expect(received[0].body.connectors[0].user_access_token).toBe(connector.userAccessToken);
|
|
510
|
+
expect(input.connectors[0]).toBe(connector);
|
|
511
|
+
expect(input.connectors[0].userAccessToken).toBe("synthetic-connector-credential");
|
|
512
|
+
const spans = captured.getFinishedSpans();
|
|
513
|
+
expect(spans).toHaveLength(1);
|
|
514
|
+
expect(JSON.stringify(spans[0].attributes)).not.toContain(connector.userAccessToken);
|
|
515
|
+
expect(wire).not.toHaveLength(0);
|
|
516
|
+
expect(JSON.stringify(wire)).not.toContain(connector.userAccessToken);
|
|
517
|
+
if (mode === "hideInputs") {
|
|
518
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.RAW_INPUT]).toBeUndefined();
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
expect(JSON.parse(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.RAW_INPUT]).connectors).toEqual([
|
|
522
|
+
{ id: connector.id, continueOnFailure: true, options: { site: "example.test" } },
|
|
523
|
+
]);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
finally {
|
|
527
|
+
yield wireProvider.shutdown();
|
|
528
|
+
}
|
|
529
|
+
}));
|
|
530
|
+
});
|
|
531
|
+
const finishCases = [
|
|
532
|
+
...[false, true].flatMap(v2 => [false, true].flatMap(streaming => (v2 ? ["COMPLETE", "STOP_SEQUENCE", "MAX_TOKENS", "TOOL_CALL", "ERROR", "TIMEOUT"]
|
|
533
|
+
: ["COMPLETE", "STOP_SEQUENCE", "MAX_TOKENS", "ERROR", "ERROR_TOXIC", "ERROR_LIMIT", "TIMEOUT", "USER_CANCEL"])
|
|
534
|
+
.map(reason => ({ label: `8.3.0 v${v2 ? 2 : 1}`, sdk: sdk8, v2, streaming, reason })))),
|
|
535
|
+
];
|
|
536
|
+
describe.each([false, true])("finish reasons with hideOutputs=%s", (hideOutputs) => {
|
|
537
|
+
it.each(finishCases)("classifies $label stream=$streaming $reason without changing SDK results", (_a) => __awaiter(void 0, [_a], void 0, function* ({ sdk, v2, streaming, reason }) {
|
|
538
|
+
var _b, e_7, _c, _d;
|
|
539
|
+
instrumentation.disable();
|
|
540
|
+
instrumentation = new instrumentation_1.CohereInstrumentation({ traceConfig: { hideOutputs } });
|
|
541
|
+
instrumentation.setTracerProvider(provider);
|
|
542
|
+
const client = clientFor(sdk, v2);
|
|
543
|
+
const failed = ["ERROR", "ERROR_TOXIC", "ERROR_LIMIT", "TIMEOUT"].includes(reason);
|
|
544
|
+
const cancelled = reason === "USER_CANCEL";
|
|
545
|
+
if (streaming) {
|
|
546
|
+
const events = streamResponse(v2);
|
|
547
|
+
if (v2)
|
|
548
|
+
events[events.length - 1].delta.finish_reason = reason;
|
|
549
|
+
else
|
|
550
|
+
events[events.length - 1].finish_reason = reason;
|
|
551
|
+
response = events.map(event => v2 ? `data: ${JSON.stringify(event)}\n\n` : `${JSON.stringify(event)}\n`).join("");
|
|
552
|
+
const delivered = [];
|
|
553
|
+
try {
|
|
554
|
+
for (var _e = true, _f = __asyncValues(yield client.chatStream(v2 ? requestV2 : request)), _g; _g = yield _f.next(), _b = _g.done, !_b; _e = true) {
|
|
555
|
+
_d = _g.value;
|
|
556
|
+
_e = false;
|
|
557
|
+
const event = _d;
|
|
558
|
+
delivered.push(event);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
catch (e_7_1) { e_7 = { error: e_7_1 }; }
|
|
562
|
+
finally {
|
|
563
|
+
try {
|
|
564
|
+
if (!_e && !_b && (_c = _f.return)) yield _c.call(_f);
|
|
565
|
+
}
|
|
566
|
+
finally { if (e_7) throw e_7.error; }
|
|
567
|
+
}
|
|
568
|
+
expect(delivered).toHaveLength(events.length);
|
|
569
|
+
const last = delivered[delivered.length - 1];
|
|
570
|
+
expect(v2 ? last.delta.finishReason : last.finishReason).toBe(reason);
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
response = Object.assign(Object.assign({}, (v2 ? v2Response : v1Response)), { finish_reason: reason });
|
|
574
|
+
const pending = client.chat(v2 ? requestV2 : request);
|
|
575
|
+
const result = yield pending;
|
|
576
|
+
expect(result.finishReason).toBe(reason);
|
|
577
|
+
expect(v2 ? result.message.content[0].text : result.text).toBe("hello");
|
|
578
|
+
expect((yield pending.withRawResponse()).data).toBe(result);
|
|
579
|
+
}
|
|
580
|
+
const spans = exporter.getFinishedSpans();
|
|
581
|
+
expect(spans).toHaveLength(1);
|
|
582
|
+
expect(spans[0].status.code).toBe(failed ? api_1.SpanStatusCode.ERROR : cancelled ? api_1.SpanStatusCode.UNSET : api_1.SpanStatusCode.OK);
|
|
583
|
+
expect(spans[0].attributes[streaming ? "cohere.stream.cancelled" : "cohere.request.cancelled"]).toBe(cancelled ? true : undefined);
|
|
584
|
+
expect(spans[0].events.filter(event => event.name === "exception")).toHaveLength(failed ? 1 : 0);
|
|
585
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBe(hideOutputs ? undefined : "hello");
|
|
586
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_TOKEN_COUNT_PROMPT]).toBe(5);
|
|
587
|
+
}));
|
|
588
|
+
});
|
|
589
|
+
it.each(["complete", "start-only", "cancelled", "eof"])("captures v2 content-start text for %s streams", (mode) => __awaiter(void 0, void 0, void 0, function* () {
|
|
590
|
+
var _a, e_8, _b, _c;
|
|
591
|
+
const client = clientFor(sdk8, true);
|
|
592
|
+
const events = [{ type: "content-start", index: 0, delta: { message: { content: { type: "text", text: "initial-" } } } }];
|
|
593
|
+
if (mode === "complete")
|
|
594
|
+
events.push({ type: "content-delta", index: 0, delta: { message: { content: { text: "final" } } } });
|
|
595
|
+
if (mode !== "eof")
|
|
596
|
+
events.push({ type: "message-end", delta: { finish_reason: "COMPLETE" } });
|
|
597
|
+
contentType = "text/event-stream";
|
|
598
|
+
response = events.map(event => `data: ${JSON.stringify(event)}\n\n`).join("");
|
|
599
|
+
const delivered = [];
|
|
600
|
+
try {
|
|
601
|
+
for (var _d = true, _e = __asyncValues(yield client.chatStream(requestV2)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
602
|
+
_c = _f.value;
|
|
603
|
+
_d = false;
|
|
604
|
+
const event = _c;
|
|
605
|
+
delivered.push(event);
|
|
606
|
+
if (mode === "cancelled")
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
611
|
+
finally {
|
|
612
|
+
try {
|
|
613
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
614
|
+
}
|
|
615
|
+
finally { if (e_8) throw e_8.error; }
|
|
616
|
+
}
|
|
617
|
+
expect(delivered[0].delta.message.content.text).toBe("initial-");
|
|
618
|
+
expect(delivered).toHaveLength(mode === "cancelled" ? 1 : events.length);
|
|
619
|
+
const spans = exporter.getFinishedSpans();
|
|
620
|
+
expect(spans).toHaveLength(1);
|
|
621
|
+
const text = mode === "complete" ? "initial-final" : "initial-";
|
|
622
|
+
expect(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBe(text);
|
|
623
|
+
expect(JSON.parse(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES])[0].content).toEqual([{ type: "text", text }]);
|
|
624
|
+
expect(JSON.parse(spans[0].attributes[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).text).toBe(text);
|
|
625
|
+
expect(spans[0].status.code).toBe(mode === "eof" ? api_1.SpanStatusCode.ERROR : mode === "cancelled" ? api_1.SpanStatusCode.UNSET : api_1.SpanStatusCode.OK);
|
|
626
|
+
}));
|
|
627
|
+
// Generated reasoning and partial tool arguments are output, not input echoes.
|
|
628
|
+
const outputModes = ["capture", "hideInputs", "hideOutputs", "hideOutputMessages", "hideOutputText"];
|
|
629
|
+
describe.each(outputModes)("generated content with %s", (mode) => {
|
|
630
|
+
const hidden = mode.startsWith("hideOutput");
|
|
631
|
+
function captureOutput(action) {
|
|
632
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
633
|
+
instrumentation.disable();
|
|
634
|
+
const captured = new sdk_trace_base_1.InMemorySpanExporter();
|
|
635
|
+
const wireProvider = new sdk_trace_node_1.NodeTracerProvider({ spanProcessors: [
|
|
636
|
+
new sdk_trace_base_1.SimpleSpanProcessor(captured),
|
|
637
|
+
new sdk_trace_base_1.SimpleSpanProcessor(new exporter_trace_otlp_http_1.OTLPTraceExporter({ url: `${url}/v1/traces` })),
|
|
638
|
+
] });
|
|
639
|
+
instrumentation = new instrumentation_1.CohereInstrumentation({ traceConfig: mode === "capture" ? {} : { [mode]: true } });
|
|
640
|
+
instrumentation.setTracerProvider(wireProvider);
|
|
641
|
+
try {
|
|
642
|
+
yield action(captured);
|
|
643
|
+
yield wireProvider.forceFlush();
|
|
644
|
+
expect(wire).not.toHaveLength(0);
|
|
645
|
+
expect(JSON.stringify(wire).includes("synthetic-generated-")).toBe(!hidden);
|
|
646
|
+
}
|
|
647
|
+
finally {
|
|
648
|
+
yield wireProvider.shutdown();
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
it.each(["chat", "complete", "cancelled", "eof", "start-only"])("preserves v2 reasoning blocks (%s)", (ending) => __awaiter(void 0, void 0, void 0, function* () {
|
|
653
|
+
yield captureOutput((captured) => __awaiter(void 0, void 0, void 0, function* () {
|
|
654
|
+
var _a, e_9, _b, _c;
|
|
655
|
+
const client = clientFor(sdk8, true);
|
|
656
|
+
const thinking = "synthetic-generated-reasoning-";
|
|
657
|
+
const blocks = [
|
|
658
|
+
{ type: "thinking", thinking: thinking + (ending === "start-only" ? "" : "continued") },
|
|
659
|
+
{ type: "text", text: "visible-answer" },
|
|
660
|
+
{ type: "thinking", thinking: "second-thought" },
|
|
661
|
+
];
|
|
662
|
+
if (ending === "chat") {
|
|
663
|
+
response = Object.assign(Object.assign({}, v2Response), { message: { role: "assistant", content: blocks } });
|
|
664
|
+
const result = yield client.chat(Object.assign(Object.assign({}, requestV2), { thinking: { type: "enabled" } }));
|
|
665
|
+
expect(result.message.content).toEqual(blocks);
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
const events = [
|
|
669
|
+
{ type: "content-start", index: 0, delta: { message: { content: { type: "thinking", thinking } } } },
|
|
670
|
+
];
|
|
671
|
+
if (ending !== "start-only")
|
|
672
|
+
events.push({ type: "content-delta", index: 0, delta: { message: { content: { thinking: "continued" } } } }, { type: "content-end", index: 0 }, { type: "content-start", index: 1, delta: { message: { content: blocks[1] } } },
|
|
673
|
+
// A delta without a start must also retain its generated content.
|
|
674
|
+
{ type: "content-delta", index: 2, delta: { message: { content: { thinking: "second-thought" } } } });
|
|
675
|
+
if (ending === "complete")
|
|
676
|
+
events.push({ type: "message-end", delta: { finish_reason: "COMPLETE" } });
|
|
677
|
+
contentType = "text/event-stream";
|
|
678
|
+
response = events.map(event => `data: ${JSON.stringify(event)}\n\n`).join("");
|
|
679
|
+
const delivered = [];
|
|
680
|
+
try {
|
|
681
|
+
for (var _d = true, _e = __asyncValues(yield client.chatStream(Object.assign(Object.assign({}, requestV2), { thinking: { type: "enabled" } }))), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
682
|
+
_c = _f.value;
|
|
683
|
+
_d = false;
|
|
684
|
+
const event = _c;
|
|
685
|
+
delivered.push(event);
|
|
686
|
+
if (ending === "cancelled" && delivered.length === 2)
|
|
687
|
+
break;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch (e_9_1) { e_9 = { error: e_9_1 }; }
|
|
691
|
+
finally {
|
|
692
|
+
try {
|
|
693
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
694
|
+
}
|
|
695
|
+
finally { if (e_9) throw e_9.error; }
|
|
696
|
+
}
|
|
697
|
+
expect(delivered[0].delta.message.content.thinking).toBe(thinking);
|
|
698
|
+
expect(delivered).toHaveLength(ending === "cancelled" ? 2 : events.length);
|
|
699
|
+
}
|
|
700
|
+
const spans = captured.getFinishedSpans();
|
|
701
|
+
expect(spans).toHaveLength(1);
|
|
702
|
+
const attrs = spans[0].attributes;
|
|
703
|
+
expect(spans[0].status.code).toBe(ending === "cancelled" ? api_1.SpanStatusCode.UNSET : ["eof", "start-only"].includes(ending) ? api_1.SpanStatusCode.ERROR : api_1.SpanStatusCode.OK);
|
|
704
|
+
if (hidden) {
|
|
705
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES]).toBeUndefined();
|
|
706
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).toBeUndefined();
|
|
707
|
+
expect(JSON.stringify(attrs)).not.toContain(thinking);
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
const expectedBlocks = ["cancelled", "start-only"].includes(ending) ? blocks.slice(0, 1) : blocks;
|
|
711
|
+
expect(JSON.parse(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES])[0].content).toEqual(expectedBlocks);
|
|
712
|
+
expect(JSON.parse(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).message.content).toEqual(expectedBlocks);
|
|
713
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).toBe(expectedBlocks.length === 1 ? "" : "visible-answer");
|
|
714
|
+
}
|
|
715
|
+
}));
|
|
716
|
+
}));
|
|
717
|
+
it.each(["chat", "complete", "cancelled", "eof"])("retains v2 generated tool plans (%s)", (ending) => __awaiter(void 0, void 0, void 0, function* () {
|
|
718
|
+
yield captureOutput((captured) => __awaiter(void 0, void 0, void 0, function* () {
|
|
719
|
+
var _a, e_10, _b, _c;
|
|
720
|
+
const client = clientFor(sdk8, true);
|
|
721
|
+
const plan = "synthetic-generated-tool-plan-continued";
|
|
722
|
+
if (ending === "chat") {
|
|
723
|
+
response = Object.assign(Object.assign({}, v2Response), { message: { role: "assistant", tool_plan: plan, tool_calls: [{ id: "call-1", type: "function", function: { name: "lookup", arguments: "{}" } }] } });
|
|
724
|
+
const result = yield client.chat(requestV2);
|
|
725
|
+
expect(result.message.toolPlan).toBe(plan);
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
const events = [
|
|
729
|
+
{ type: "tool-plan-delta", delta: { message: { tool_plan: "synthetic-generated-tool-plan-" } } },
|
|
730
|
+
{ type: "tool-plan-delta", delta: { message: { tool_plan: "continued" } } },
|
|
731
|
+
{ type: "tool-call-start", index: 0, delta: { message: { tool_calls: { id: "call-1", type: "function", function: { name: "lookup", arguments: "{}" } } } } },
|
|
732
|
+
];
|
|
733
|
+
if (ending === "complete")
|
|
734
|
+
events.push({ type: "message-end", delta: { finish_reason: "TOOL_CALL" } });
|
|
735
|
+
contentType = "text/event-stream";
|
|
736
|
+
response = events.map(event => `data: ${JSON.stringify(event)}\n\n`).join("");
|
|
737
|
+
const delivered = [];
|
|
738
|
+
try {
|
|
739
|
+
for (var _d = true, _e = __asyncValues(yield client.chatStream(requestV2)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
740
|
+
_c = _f.value;
|
|
741
|
+
_d = false;
|
|
742
|
+
const event = _c;
|
|
743
|
+
delivered.push(event);
|
|
744
|
+
if (ending === "cancelled" && delivered.length === 3)
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
catch (e_10_1) { e_10 = { error: e_10_1 }; }
|
|
749
|
+
finally {
|
|
750
|
+
try {
|
|
751
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
752
|
+
}
|
|
753
|
+
finally { if (e_10) throw e_10.error; }
|
|
754
|
+
}
|
|
755
|
+
expect(delivered[0].delta.message.toolPlan).toBe("synthetic-generated-tool-plan-");
|
|
756
|
+
}
|
|
757
|
+
const spans = captured.getFinishedSpans();
|
|
758
|
+
expect(spans).toHaveLength(1);
|
|
759
|
+
const attrs = spans[0].attributes;
|
|
760
|
+
expect(spans[0].status.code).toBe(ending === "cancelled" ? api_1.SpanStatusCode.UNSET : ending === "eof" ? api_1.SpanStatusCode.ERROR : api_1.SpanStatusCode.OK);
|
|
761
|
+
if (hidden) {
|
|
762
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES]).toBeUndefined();
|
|
763
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).toBeUndefined();
|
|
764
|
+
expect(JSON.stringify(attrs)).not.toContain("synthetic-generated-");
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
expect(JSON.parse(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).message.toolPlan).toBe(plan);
|
|
768
|
+
expect(JSON.parse(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES])[0].tool_plan).toBe(plan);
|
|
769
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.OUTPUT_VALUE]).not.toContain(plan);
|
|
770
|
+
}
|
|
771
|
+
}));
|
|
772
|
+
}));
|
|
773
|
+
it.each(["cancelled", "eof", "terminal", "generation"])("retains v1 generated tool plans (%s)", (ending) => __awaiter(void 0, void 0, void 0, function* () {
|
|
774
|
+
yield captureOutput((captured) => __awaiter(void 0, void 0, void 0, function* () {
|
|
775
|
+
var _a, e_11, _b, _c;
|
|
776
|
+
const client = clientFor(sdk8);
|
|
777
|
+
const partial = "synthetic-generated-tool-plan-continued";
|
|
778
|
+
const events = [
|
|
779
|
+
{ event_type: "tool-calls-chunk", tool_call_delta: { index: 0, text: "synthetic-generated-tool-plan-", name: "lookup", parameters: "{}" } },
|
|
780
|
+
{ event_type: "tool-calls-chunk", tool_call_delta: { index: 0, text: "continued" } },
|
|
781
|
+
];
|
|
782
|
+
if (ending === "generation")
|
|
783
|
+
events.push({ event_type: "tool-calls-generation", text: partial, tool_calls: [{ name: "lookup", parameters: {} }] });
|
|
784
|
+
if (ending === "terminal")
|
|
785
|
+
events.push({ event_type: "stream-end", finish_reason: "COMPLETE", response: { text: partial, tool_calls: [{ name: "lookup", parameters: {} }] } });
|
|
786
|
+
contentType = "application/x-ndjson";
|
|
787
|
+
response = events.map(event => `${JSON.stringify(event)}\n`).join("");
|
|
788
|
+
const delivered = [];
|
|
789
|
+
try {
|
|
790
|
+
for (var _d = true, _e = __asyncValues(yield client.chatStream(request)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
791
|
+
_c = _f.value;
|
|
792
|
+
_d = false;
|
|
793
|
+
const event = _c;
|
|
794
|
+
delivered.push(event);
|
|
795
|
+
if (ending === "cancelled" && delivered.length === 2)
|
|
796
|
+
break;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
catch (e_11_1) { e_11 = { error: e_11_1 }; }
|
|
800
|
+
finally {
|
|
801
|
+
try {
|
|
802
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
803
|
+
}
|
|
804
|
+
finally { if (e_11) throw e_11.error; }
|
|
805
|
+
}
|
|
806
|
+
expect(delivered[0].toolCallDelta.text).toBe("synthetic-generated-tool-plan-");
|
|
807
|
+
const spans = captured.getFinishedSpans();
|
|
808
|
+
expect(spans).toHaveLength(1);
|
|
809
|
+
const attrs = spans[0].attributes;
|
|
810
|
+
expect(spans[0].status.code).toBe(ending === "terminal" ? api_1.SpanStatusCode.OK : ending === "cancelled" ? api_1.SpanStatusCode.UNSET : api_1.SpanStatusCode.ERROR);
|
|
811
|
+
if (hidden) {
|
|
812
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES]).toBeUndefined();
|
|
813
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).toBeUndefined();
|
|
814
|
+
expect(JSON.stringify(attrs)).not.toContain("synthetic-generated-");
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
expect(JSON.parse(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).message.toolPlan).toBe(partial);
|
|
818
|
+
expect(JSON.parse(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES])[0].tool_plan).toBe(partial);
|
|
819
|
+
}
|
|
820
|
+
}));
|
|
821
|
+
}));
|
|
822
|
+
it.each(["cancelled", "eof", "terminal", "generation"])("preserves v1 tool chunks (%s)", (ending) => __awaiter(void 0, void 0, void 0, function* () {
|
|
823
|
+
yield captureOutput((captured) => __awaiter(void 0, void 0, void 0, function* () {
|
|
824
|
+
var _a, e_12, _b, _c;
|
|
825
|
+
const client = clientFor(sdk8);
|
|
826
|
+
// Interleave calls and leave one argument incomplete: telemetry must not
|
|
827
|
+
// require JSON parsing, mix call indices, or quote an already-raw string.
|
|
828
|
+
const events = [
|
|
829
|
+
{ event_type: "tool-calls-chunk", tool_call_delta: { index: 1, name: "second", parameters: '{"synthetic-generated-second":' } },
|
|
830
|
+
{ event_type: "tool-calls-chunk", tool_call_delta: { index: 0, name: "lookup", parameters: '{"synthetic-generated-key":' } },
|
|
831
|
+
{ event_type: "tool-calls-chunk", tool_call_delta: { index: 0, parameters: '"value"}' } },
|
|
832
|
+
];
|
|
833
|
+
const finalCalls = [{ name: "authoritative", parameters: { "synthetic-generated-final": true } }];
|
|
834
|
+
if (ending === "terminal")
|
|
835
|
+
events.push({ event_type: "stream-end", finish_reason: "COMPLETE", response: { text: "", tool_calls: finalCalls } });
|
|
836
|
+
if (ending === "generation")
|
|
837
|
+
events.push({ event_type: "tool-calls-generation", tool_calls: finalCalls });
|
|
838
|
+
contentType = "application/x-ndjson";
|
|
839
|
+
response = events.map(event => `${JSON.stringify(event)}\n`).join("");
|
|
840
|
+
const delivered = [];
|
|
841
|
+
try {
|
|
842
|
+
for (var _d = true, _e = __asyncValues(yield client.chatStream(request)), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
|
|
843
|
+
_c = _f.value;
|
|
844
|
+
_d = false;
|
|
845
|
+
const event = _c;
|
|
846
|
+
delivered.push(event);
|
|
847
|
+
if (ending === "cancelled" && delivered.length === 3)
|
|
848
|
+
break;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
catch (e_12_1) { e_12 = { error: e_12_1 }; }
|
|
852
|
+
finally {
|
|
853
|
+
try {
|
|
854
|
+
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
|
|
855
|
+
}
|
|
856
|
+
finally { if (e_12) throw e_12.error; }
|
|
857
|
+
}
|
|
858
|
+
expect(delivered[1].toolCallDelta.name).toBe("lookup");
|
|
859
|
+
expect(delivered[2].toolCallDelta.parameters).toBe('"value"}');
|
|
860
|
+
expect(delivered).toHaveLength(events.length);
|
|
861
|
+
const spans = captured.getFinishedSpans();
|
|
862
|
+
expect(spans).toHaveLength(1);
|
|
863
|
+
const attrs = spans[0].attributes;
|
|
864
|
+
expect(spans[0].status.code).toBe(ending === "terminal" ? api_1.SpanStatusCode.OK : ending === "cancelled" ? api_1.SpanStatusCode.UNSET : api_1.SpanStatusCode.ERROR);
|
|
865
|
+
if (hidden) {
|
|
866
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES]).toBeUndefined();
|
|
867
|
+
expect(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).toBeUndefined();
|
|
868
|
+
expect(JSON.stringify(attrs)).not.toContain("synthetic-generated-");
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
const calls = ["terminal", "generation"].includes(ending) ? finalCalls : [
|
|
872
|
+
{ name: "lookup", parameters: '{"synthetic-generated-key":"value"}' },
|
|
873
|
+
{ name: "second", parameters: '{"synthetic-generated-second":' },
|
|
874
|
+
];
|
|
875
|
+
expect(JSON.parse(attrs[fi_semantic_conventions_1.SemanticConventions.RAW_OUTPUT]).toolCalls).toEqual(calls);
|
|
876
|
+
expect(JSON.parse(attrs[fi_semantic_conventions_1.SemanticConventions.LLM_OUTPUT_MESSAGES])[0].tool_calls).toEqual(calls.map(call => ({
|
|
877
|
+
function: { name: call.name, arguments: typeof call.parameters === "string" ? call.parameters : JSON.stringify(call.parameters) },
|
|
878
|
+
})));
|
|
879
|
+
}
|
|
880
|
+
}));
|
|
881
|
+
}));
|
|
882
|
+
});
|
|
883
|
+
//# sourceMappingURL=sdk.test.js.map
|