next-observer 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +80 -2
- package/dist/agents/cards.d.ts +14 -0
- package/dist/agents/cards.js +85 -0
- package/dist/agents/cards.js.map +1 -0
- package/dist/agents/chat.d.ts +21 -0
- package/dist/agents/chat.js +83 -0
- package/dist/agents/chat.js.map +1 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/index.js +8 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/investigator.d.ts +50 -0
- package/dist/agents/investigator.js +123 -0
- package/dist/agents/investigator.js.map +1 -0
- package/dist/agents/mock-llm.d.ts +7 -0
- package/dist/agents/mock-llm.js +46 -0
- package/dist/agents/mock-llm.js.map +1 -0
- package/dist/agents/model.d.ts +5 -0
- package/dist/agents/model.js +20 -0
- package/dist/agents/model.js.map +1 -0
- package/dist/agents/specialists.d.ts +20 -0
- package/dist/agents/specialists.js +74 -0
- package/dist/agents/specialists.js.map +1 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +20 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +231 -0
- package/dist/cli.js.map +1 -0
- package/dist/collector/chat.d.ts +93 -0
- package/dist/collector/chat.js +2 -0
- package/dist/collector/chat.js.map +1 -0
- package/dist/collector/decode.d.ts +52 -0
- package/dist/collector/decode.js +59 -0
- package/dist/collector/decode.js.map +1 -0
- package/dist/collector/framework.d.ts +7 -0
- package/dist/collector/framework.js +9 -0
- package/dist/collector/framework.js.map +1 -0
- package/dist/collector/index.d.ts +6 -0
- package/dist/collector/index.js +5 -0
- package/dist/collector/index.js.map +1 -0
- package/dist/collector/memory-storage.d.ts +19 -0
- package/dist/collector/memory-storage.js +162 -0
- package/dist/collector/memory-storage.js.map +1 -0
- package/dist/collector/server.d.ts +31 -0
- package/dist/collector/server.js +266 -0
- package/dist/collector/server.js.map +1 -0
- package/dist/collector/static.d.ts +3 -0
- package/dist/collector/static.js +43 -0
- package/dist/collector/static.js.map +1 -0
- package/dist/collector/types.d.ts +105 -0
- package/dist/collector/types.js +2 -0
- package/dist/collector/types.js.map +1 -0
- package/dist/debug/demo.d.ts +4 -0
- package/dist/debug/demo.js +90 -0
- package/dist/debug/demo.js.map +1 -0
- package/dist/debug/detector.d.ts +61 -0
- package/dist/debug/detector.js +116 -0
- package/dist/debug/detector.js.map +1 -0
- package/dist/debug/index.d.ts +3 -0
- package/dist/debug/index.js +5 -0
- package/dist/debug/index.js.map +1 -0
- package/dist/debug/queries.d.ts +199 -0
- package/dist/debug/queries.js +235 -0
- package/dist/debug/queries.js.map +1 -0
- package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/ui/assets/index-Y3VIawX_.css +2 -0
- package/dist/ui/assets/index-vwPciq9g.js +11 -0
- package/dist/ui/index.html +13 -0
- package/package.json +72 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrei Tazetdinov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# next-observer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The observer for **[next-observe](https://www.npmjs.com/package/next-observe)**: an OTLP collector, a trace UI, an anomaly
|
|
4
|
+
detector and AI agents (Google ADK) that investigate your Next.js app on their own. Run it with `npx` — it never goes
|
|
5
|
+
into your app's dependencies.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx next-observer dev # observer + UI on http://127.0.0.1:4318, then `next dev` in the current directory
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- **Trace UI** — list + waterfall, no Docker, no external backend
|
|
12
|
+
- **Chat with agents** that query your telemetry: slow operations, regressions between deployments, failing operations
|
|
13
|
+
with exact errors, N+1 patterns — with evidence cards built from the data, not by the model
|
|
14
|
+
- **Anomaly detector** that starts an investigation by itself when error rates or latency jump
|
|
15
|
+
|
|
16
|
+
Requires Node.js 22.18+. The app is instrumented with `next-observe` (see its README).
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
next-observer dev [--root <dir>] [--port <n>] [-- <next dev args>] # observer + next dev
|
|
22
|
+
next-observer collector [--host <h>] [--port <n>] [--api-key <k>] [--demo]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Run them with `npx next-observer <command>`; installed globally (`npm i -g next-observer`) the command is also
|
|
26
|
+
available as `nxo`.
|
|
27
|
+
|
|
28
|
+
`next-observer collector` runs the observer alone — next to `next dev` in another terminal, or on a server for production.
|
|
29
|
+
`--demo` preloads a sample "shop" scenario (a regression between versions, a failing endpoint, an N+1).
|
|
30
|
+
|
|
31
|
+
## Models
|
|
32
|
+
|
|
33
|
+
| `OBSERVE_AI` | Model |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `mock` (default) | deterministic stand-in — the whole pipeline runs without tokens |
|
|
36
|
+
| `real` | Gemini when `GEMINI_API_KEY` + `GEMINI_MODEL` are set, otherwise [Kitana](https://www.npmjs.com/package/@kitana-sdk/adk) (Claude / Codex CLI, Ollama, API keys) |
|
|
37
|
+
|
|
38
|
+
## Your own specialists
|
|
39
|
+
|
|
40
|
+
Put `observe.agents.ts` (or `.mts`, `.js`, `.mjs`) in the app root. A specialist with a built-in name
|
|
41
|
+
(`latency_agent`, `error_agent`, `traffic_agent`) replaces it; a new name adds one. The orchestrator picks specialists by
|
|
42
|
+
their `description`; the rules "facts from tools only" and "own tools only" are added to every instruction.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { defineSpecialist } from 'next-observe/agents'
|
|
46
|
+
|
|
47
|
+
export default [
|
|
48
|
+
defineSpecialist({
|
|
49
|
+
name: 'latency_agent',
|
|
50
|
+
description: 'Finds slow operations and the deployment that made them slow',
|
|
51
|
+
instruction: 'First call compare_versions; then open one slow trace and name the span with the highest selfMs.',
|
|
52
|
+
tools: ['compare_versions', 'search_traces', 'get_trace'],
|
|
53
|
+
}),
|
|
54
|
+
]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Tools: `get_services`, `get_operation_stats`, `compare_versions`, `get_errors`, `search_traces`, `get_trace`.
|
|
58
|
+
`next-observer` validates the file and lists the loaded specialists on start.
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
| Variable | Default | |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `OBSERVE_AI` | `mock` | `mock` \| `real` |
|
|
65
|
+
| `OBSERVE_DETECTOR` | on | `off` disables the anomaly detector |
|
|
66
|
+
| `OBSERVE_API_KEY` | — | require `x-api-key` on ingest and API |
|
|
67
|
+
| `OBSERVE_PORT`, `OBSERVE_HOST`, `OBSERVE_ROOT` | `4318`, `127.0.0.1`, `.` | same as `--port`, `--host`, `--root` |
|
|
68
|
+
|
|
69
|
+
Accepts OTLP/HTTP **JSON** (what `next-observe` sends); protobuf is planned.
|
|
70
|
+
|
|
71
|
+
## Entry points
|
|
72
|
+
|
|
73
|
+
| Import | For |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `next-observer/collector` | `startCollector()`, storage, OTLP/JSON decoding |
|
|
76
|
+
| `next-observer/debug` | agent tools as plain functions, `AnomalyDetector`, demo data |
|
|
77
|
+
| `next-observer/agents` | ADK agents, chat handler, built-in specialists |
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EvidenceCard } from '../collector/chat.js';
|
|
2
|
+
export declare const THRESHOLDS: {
|
|
3
|
+
/** p95 latest / previous version. */
|
|
4
|
+
regressionRatio: number;
|
|
5
|
+
/** errorRate latest − previous version. */
|
|
6
|
+
regressionErrorDelta: number;
|
|
7
|
+
errorRate: number;
|
|
8
|
+
/** One span's self time as a share of the whole trace. */
|
|
9
|
+
hotspotShare: number;
|
|
10
|
+
silentSeconds: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function cardsFromResult(tool: string, args: Record<string, unknown>, result: unknown): EvidenceCard[];
|
|
13
|
+
/** Same finding reported twice in one turn (e.g. two agents compared versions) shows once. */
|
|
14
|
+
export declare function cardKey(card: EvidenceCard): string;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export const THRESHOLDS = {
|
|
2
|
+
/** p95 latest / previous version. */
|
|
3
|
+
regressionRatio: 1.5,
|
|
4
|
+
/** errorRate latest − previous version. */
|
|
5
|
+
regressionErrorDelta: 0.1,
|
|
6
|
+
errorRate: 0.05,
|
|
7
|
+
/** One span's self time as a share of the whole trace. */
|
|
8
|
+
hotspotShare: 0.5,
|
|
9
|
+
silentSeconds: 120,
|
|
10
|
+
};
|
|
11
|
+
function describeFilters(args) {
|
|
12
|
+
const parts = Object.entries(args)
|
|
13
|
+
.filter(([, v]) => v !== undefined && v !== '')
|
|
14
|
+
.map(([k, v]) => `${k}=${String(v)}`);
|
|
15
|
+
return parts.length ? `Traces · ${parts.join(', ')}` : 'Recent traces';
|
|
16
|
+
}
|
|
17
|
+
export function cardsFromResult(tool, args, result) {
|
|
18
|
+
switch (tool) {
|
|
19
|
+
case 'compare_versions': {
|
|
20
|
+
const { changes } = result;
|
|
21
|
+
return changes
|
|
22
|
+
.filter((c) => (c.p95Ratio ?? 0) >= THRESHOLDS.regressionRatio || c.errorRateDelta >= THRESHOLDS.regressionErrorDelta)
|
|
23
|
+
.map((c) => ({ kind: 'regression', service: c.service, operation: c.operation, from: c.from, to: c.to, p95Ratio: c.p95Ratio, errorRateDelta: c.errorRateDelta }));
|
|
24
|
+
}
|
|
25
|
+
case 'get_errors': {
|
|
26
|
+
const { errors } = result;
|
|
27
|
+
return errors
|
|
28
|
+
.filter((e) => (e.errorRate ?? 1) >= THRESHOLDS.errorRate)
|
|
29
|
+
.map((e) => ({
|
|
30
|
+
kind: 'errors',
|
|
31
|
+
service: e.service,
|
|
32
|
+
operation: e.operation,
|
|
33
|
+
errorRate: e.errorRate,
|
|
34
|
+
errors: e.errors,
|
|
35
|
+
message: e.topMessages[0]?.message ?? '(no message)',
|
|
36
|
+
traceIds: e.exampleTraceIds,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
case 'get_trace': {
|
|
40
|
+
const trace = result;
|
|
41
|
+
if (!('traceId' in trace) || trace.spans.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const cards = trace.repeated.map((r) => ({ kind: 'n-plus-one', traceId: trace.traceId, parent: r.parent, operation: r.operation, count: r.count, totalMs: r.totalMs }));
|
|
44
|
+
const traceMs = trace.spans[0].durationMs;
|
|
45
|
+
const hottest = trace.spans
|
|
46
|
+
.filter((s) => s.depth > 0 && typeof s.attributes?.['code.filepath'] === 'string')
|
|
47
|
+
.sort((a, b) => b.selfMs - a.selfMs)[0];
|
|
48
|
+
if (hottest && traceMs > 0 && hottest.selfMs / traceMs >= THRESHOLDS.hotspotShare) {
|
|
49
|
+
cards.push({ kind: 'hotspot', traceId: trace.traceId, operation: hottest.name, selfMs: hottest.selfMs, traceMs, codeFile: String(hottest.attributes['code.filepath']) });
|
|
50
|
+
}
|
|
51
|
+
return cards;
|
|
52
|
+
}
|
|
53
|
+
case 'get_services': {
|
|
54
|
+
const { services } = result;
|
|
55
|
+
return services
|
|
56
|
+
.filter((s) => s.lastSpanSecondsAgo > THRESHOLDS.silentSeconds)
|
|
57
|
+
.map((s) => ({ kind: 'silent', service: s.name, lastSpanSecondsAgo: s.lastSpanSecondsAgo }));
|
|
58
|
+
}
|
|
59
|
+
case 'search_traces': {
|
|
60
|
+
const { traces } = result;
|
|
61
|
+
if (traces.length === 0)
|
|
62
|
+
return [];
|
|
63
|
+
return [{ kind: 'traces', label: describeFilters(args), traces: traces.slice(0, 5).map((t) => ({ traceId: t.traceId, root: t.root, durationMs: t.durationMs, errors: t.errors })) }];
|
|
64
|
+
}
|
|
65
|
+
default:
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Same finding reported twice in one turn (e.g. two agents compared versions) shows once. */
|
|
70
|
+
export function cardKey(card) {
|
|
71
|
+
switch (card.kind) {
|
|
72
|
+
case 'regression':
|
|
73
|
+
case 'errors':
|
|
74
|
+
return `${card.kind}:${card.service}:${card.operation}`;
|
|
75
|
+
case 'n-plus-one':
|
|
76
|
+
return `${card.kind}:${card.parent}:${card.operation}`;
|
|
77
|
+
case 'hotspot':
|
|
78
|
+
return `${card.kind}:${card.operation}:${card.codeFile}`;
|
|
79
|
+
case 'silent':
|
|
80
|
+
return `${card.kind}:${card.service}`;
|
|
81
|
+
case 'traces':
|
|
82
|
+
return `${card.kind}:${card.label}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=cards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cards.js","sourceRoot":"","sources":["../../src/agents/cards.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BaseLlm } from '@google/adk';
|
|
2
|
+
import type { ChatHandler } from '../collector/chat.js';
|
|
3
|
+
import type { StorageAdapter } from '../collector/types.js';
|
|
4
|
+
import type { QueryOptions } from '../debug/queries.js';
|
|
5
|
+
import type { SpecialistSpec } from './specialists.js';
|
|
6
|
+
import { type Env } from './model.js';
|
|
7
|
+
export interface ChatHandlerOptions {
|
|
8
|
+
storage: StorageAdapter;
|
|
9
|
+
env?: Env;
|
|
10
|
+
queryOptions?: QueryOptions;
|
|
11
|
+
/** An investigation that takes longer ends with an error event. Default 180 s — a CLI model can hang. */
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
/** Overrides the model chosen from env (tests, custom providers). */
|
|
14
|
+
model?: BaseLlm | string;
|
|
15
|
+
/** Default: the built-in specialists; the CLI merges in the project's observe.agents file. */
|
|
16
|
+
specialists?: SpecialistSpec[];
|
|
17
|
+
}
|
|
18
|
+
export declare function createChatHandler(options: ChatHandlerOptions): Promise<{
|
|
19
|
+
mode: 'mock' | 'real';
|
|
20
|
+
handle: ChatHandler;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { cardKey, cardsFromResult } from './cards.js';
|
|
2
|
+
import { createInvestigator } from './investigator.js';
|
|
3
|
+
import { getModel, resolveAiMode } from './model.js';
|
|
4
|
+
export async function createChatHandler(options) {
|
|
5
|
+
const env = options.env ?? process.env;
|
|
6
|
+
const mode = resolveAiMode(env);
|
|
7
|
+
const investigator = createInvestigator({
|
|
8
|
+
storage: options.storage,
|
|
9
|
+
model: options.model ?? (await getModel(env)),
|
|
10
|
+
queryOptions: options.queryOptions,
|
|
11
|
+
specialists: options.specialists,
|
|
12
|
+
});
|
|
13
|
+
const timeoutMs = options.timeoutMs ?? 180_000;
|
|
14
|
+
// One question at a time per conversation: a second one would interleave with the first in the session history.
|
|
15
|
+
const busy = new Set();
|
|
16
|
+
const handle = async ({ question, sessionId: requested }, emit) => {
|
|
17
|
+
// Reserve synchronously, before any await: two requests for the same session must not both get through.
|
|
18
|
+
if (requested) {
|
|
19
|
+
if (busy.has(requested)) {
|
|
20
|
+
emit({ type: 'error', message: 'this chat is still answering the previous question' });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
busy.add(requested);
|
|
24
|
+
}
|
|
25
|
+
let sessionId = requested;
|
|
26
|
+
let investigation;
|
|
27
|
+
let timedOut = false;
|
|
28
|
+
let finished = false;
|
|
29
|
+
const send = (event) => {
|
|
30
|
+
if (!finished)
|
|
31
|
+
emit(event);
|
|
32
|
+
};
|
|
33
|
+
let timer;
|
|
34
|
+
const timeout = new Promise((resolve) => (timer = setTimeout(() => resolve('timeout'), timeoutMs)));
|
|
35
|
+
const shown = new Set();
|
|
36
|
+
const onResult = (step, result) => {
|
|
37
|
+
for (const card of cardsFromResult(step.tool, step.args, result)) {
|
|
38
|
+
const key = cardKey(card);
|
|
39
|
+
if (shown.has(key))
|
|
40
|
+
continue;
|
|
41
|
+
shown.add(key);
|
|
42
|
+
send({ type: 'card', card });
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
sessionId = await investigator.startSession(requested);
|
|
47
|
+
busy.add(sessionId);
|
|
48
|
+
emit({ type: 'status', mode, sessionId, text: 'Investigating…' });
|
|
49
|
+
const running = investigator.ask(question, { sessionId, onStep: (s) => send({ type: 'step', ...s }), onResult });
|
|
50
|
+
investigation = running;
|
|
51
|
+
const result = await Promise.race([running, timeout]);
|
|
52
|
+
if (result === 'timeout') {
|
|
53
|
+
timedOut = true;
|
|
54
|
+
send({ type: 'error', message: `investigation took longer than ${Math.round(timeoutMs / 1000)}s — try a narrower question` });
|
|
55
|
+
}
|
|
56
|
+
else if (result.error && !result.text) {
|
|
57
|
+
send({ type: 'error', message: result.error });
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
send({ type: 'report', text: result.text || '(the agents returned no report)' });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
send({ type: 'error', message: error instanceof Error ? error.message : String(error) });
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
// A timed-out investigation keeps running and writing to the session: its late steps must not leak into this
|
|
68
|
+
// finished turn, and the session stays busy until it really ends.
|
|
69
|
+
finished = true;
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
const release = () => {
|
|
72
|
+
if (sessionId)
|
|
73
|
+
busy.delete(sessionId);
|
|
74
|
+
};
|
|
75
|
+
if (timedOut && investigation)
|
|
76
|
+
investigation.then(release, release);
|
|
77
|
+
else
|
|
78
|
+
release();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
return { mode, handle };
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=chat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/agents/chat.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createInvestigator, isLeakedToolCall, type Investigator, type InvestigatorOptions, type InvestigationStep, type TranscriptEntry } from './investigator.js';
|
|
2
|
+
export { getModel, resolveAiMode, type AiMode } from './model.js';
|
|
3
|
+
export { MockLlm, MOCK_PREFIX } from './mock-llm.js';
|
|
4
|
+
export { createChatHandler, type ChatHandlerOptions } from './chat.js';
|
|
5
|
+
export { cardsFromResult, cardKey, THRESHOLDS } from './cards.js';
|
|
6
|
+
export { BUILT_IN_SPECIALISTS, TOOL_NAMES, defineSpecialist, mergeSpecialists, type SpecialistSpec, type ToolName } from './specialists.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// ADK agents over the telemetry tools (Gemini, Kitana or the mock model).
|
|
2
|
+
export { createInvestigator, isLeakedToolCall } from './investigator.js';
|
|
3
|
+
export { getModel, resolveAiMode } from './model.js';
|
|
4
|
+
export { MockLlm, MOCK_PREFIX } from './mock-llm.js';
|
|
5
|
+
export { createChatHandler } from './chat.js';
|
|
6
|
+
export { cardsFromResult, cardKey, THRESHOLDS } from './cards.js';
|
|
7
|
+
export { BUILT_IN_SPECIALISTS, TOOL_NAMES, defineSpecialist, mergeSpecialists } from './specialists.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agents/index.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAA6F,MAAM,mBAAmB,CAAA;AACnK,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAe,MAAM,YAAY,CAAA;AACjE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,iBAAiB,EAA2B,MAAM,WAAW,CAAA;AACtE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AACjE,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAsC,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type BaseLlm } from '@google/adk';
|
|
2
|
+
import type { StorageAdapter } from '../collector/types.js';
|
|
3
|
+
import { type QueryOptions } from '../debug/queries.js';
|
|
4
|
+
import { type SpecialistSpec } from './specialists.js';
|
|
5
|
+
export interface InvestigationStep {
|
|
6
|
+
agent: string;
|
|
7
|
+
tool: string;
|
|
8
|
+
args: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
/** What the orchestrator did, in order: specialist calls, their answers, its own text. */
|
|
11
|
+
export interface TranscriptEntry {
|
|
12
|
+
kind: 'call' | 'result' | 'text';
|
|
13
|
+
agent: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
export interface InvestigatorOptions {
|
|
17
|
+
storage: StorageAdapter;
|
|
18
|
+
model: BaseLlm | string;
|
|
19
|
+
queryOptions?: QueryOptions;
|
|
20
|
+
/** Called for every tool call, specialists included — the chat shows these while the agents work. */
|
|
21
|
+
onStep?: (step: InvestigationStep) => void;
|
|
22
|
+
/** Default: the built-in latency, error and traffic specialists. */
|
|
23
|
+
specialists?: SpecialistSpec[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A final text that starts like `{"tool_call": …}` is a tool call that leaked instead of a report. Seen in real runs:
|
|
27
|
+
* the model dropped a closing brace, the provider could not parse the call, and the raw JSON came back as text —
|
|
28
|
+
* so this checks the shape of the start, not whether the JSON is valid.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isLeakedToolCall(text: string): boolean;
|
|
31
|
+
export declare function createInvestigator(options: InvestigatorOptions): {
|
|
32
|
+
startSession: (sessionId?: string) => Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Runs one investigation; resolves with the final report and every tool call made on the way.
|
|
35
|
+
* Pass the sessionId from a previous answer to continue that conversation. Safe to call concurrently (per session: one at a time).
|
|
36
|
+
*/
|
|
37
|
+
ask(question: string, call?: {
|
|
38
|
+
onStep?: (step: InvestigationStep) => void;
|
|
39
|
+
/** Called with each data tool's result — the chat builds evidence cards from these. */
|
|
40
|
+
onResult?: (step: InvestigationStep, result: unknown) => void;
|
|
41
|
+
sessionId?: string;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
text: string;
|
|
44
|
+
sessionId: string;
|
|
45
|
+
steps: InvestigationStep[];
|
|
46
|
+
transcript: TranscriptEntry[];
|
|
47
|
+
error?: string;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
export type Investigator = ReturnType<typeof createInvestigator>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Orchestrator + specialist agents (Four Golden Signals split) over the telemetry tools from ../debug.
|
|
2
|
+
import { AgentTool, FunctionTool, InMemorySessionService, LlmAgent, Runner } from '@google/adk';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { createAgentQueries } from '../debug/queries.js';
|
|
5
|
+
import { BUILT_IN_SPECIALISTS, orchestratorInstruction, specialistInstruction } from './specialists.js';
|
|
6
|
+
const since = z.number().positive().optional().describe('look back this many minutes (default 15)');
|
|
7
|
+
const service = z.string().optional().describe('exact service name');
|
|
8
|
+
const operation = z.string().optional().describe('substring of the operation name, case-insensitive');
|
|
9
|
+
/**
|
|
10
|
+
* A final text that starts like `{"tool_call": …}` is a tool call that leaked instead of a report. Seen in real runs:
|
|
11
|
+
* the model dropped a closing brace, the provider could not parse the call, and the raw JSON came back as text —
|
|
12
|
+
* so this checks the shape of the start, not whether the JSON is valid.
|
|
13
|
+
*/
|
|
14
|
+
export function isLeakedToolCall(text) {
|
|
15
|
+
return /^\s*(?:```(?:json)?\s*)?\{\s*"tool_call"\s*:/.test(text);
|
|
16
|
+
}
|
|
17
|
+
const APP_NAME = 'next-observe';
|
|
18
|
+
const USER_ID = 'next-observe';
|
|
19
|
+
export function createInvestigator(options) {
|
|
20
|
+
const q = createAgentQueries(options.storage, options.queryOptions);
|
|
21
|
+
const specialists = options.specialists ?? BUILT_IN_SPECIALISTS;
|
|
22
|
+
// Conversation memory: ADK stores every question, specialist call and answer in the session; the orchestrator sees
|
|
23
|
+
// them on the next turn. Specialists (AgentTool) start fresh each time and get context via the orchestrator's request.
|
|
24
|
+
// In-memory: sessions end with the process.
|
|
25
|
+
const sessions = new InMemorySessionService();
|
|
26
|
+
/** Continues a session, or starts one (also when an id is unknown, e.g. after a restart). Returns the session id. */
|
|
27
|
+
async function startSession(sessionId) {
|
|
28
|
+
if (sessionId) {
|
|
29
|
+
const existing = await sessions.getSession({ appName: APP_NAME, userId: USER_ID, sessionId });
|
|
30
|
+
if (existing)
|
|
31
|
+
return existing.id;
|
|
32
|
+
}
|
|
33
|
+
return (await sessions.createSession({ appName: APP_NAME, userId: USER_ID, sessionId })).id;
|
|
34
|
+
}
|
|
35
|
+
// Agents are built per investigation so each ask() records into its own steps — concurrent chats don't mix.
|
|
36
|
+
function buildOrchestrator(record, recordResult) {
|
|
37
|
+
// Tools are created per agent so every step is attributed to the agent that made it.
|
|
38
|
+
function tool(agent, name, description, parameters, run) {
|
|
39
|
+
return new FunctionTool({
|
|
40
|
+
name,
|
|
41
|
+
description,
|
|
42
|
+
parameters,
|
|
43
|
+
execute: async (args) => {
|
|
44
|
+
const step = { agent, tool: name, args: args };
|
|
45
|
+
record(step);
|
|
46
|
+
const result = await run(args);
|
|
47
|
+
recordResult(step, result);
|
|
48
|
+
return result;
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const tools = (agent) => ({
|
|
53
|
+
get_services: tool(agent, 'get_services', 'Services, their deployed versions (in deploy order) and seconds since their last span.', z.object({}), () => q.getServices()),
|
|
54
|
+
get_operation_stats: tool(agent, 'get_operation_stats', 'Slowest operations by p95 with p50/p99 and error rate.', z.object({ service, operation, sinceMinutes: since }), (a) => q.getOperationStats(a)),
|
|
55
|
+
compare_versions: tool(agent, 'compare_versions', 'Latest deployed version vs the previous one per operation: p95 ratio and error-rate delta. Use it to name the deployment that caused a regression.', z.object({ service, operation, sinceMinutes: since }), (a) => q.compareVersions(a)),
|
|
56
|
+
get_errors: tool(agent, 'get_errors', 'Failing operations: error rate, top exception messages, example trace ids.', z.object({ service, operation, sinceMinutes: since }), (a) => q.getErrors(a)),
|
|
57
|
+
search_traces: tool(agent, 'search_traces', 'Recent traces matching filters, to get example trace ids.', z.object({ service, operation, minDurationMs: z.number().optional(), hasError: z.boolean().optional(), sinceMinutes: since }), (a) => q.searchTraces(a)),
|
|
58
|
+
get_trace: tool(agent, 'get_trace', 'One trace as a tree with self time, errors and code file paths; `repeated` lists ≥3 identical sibling calls (N+1); `nextInternalMs` is time inside Next.js itself (routing, rendering, dev compilation), not the app code.', z.object({ traceId: z.string().describe('32-char hex trace id from search_traces') }), (a) => q.getTrace(a)),
|
|
59
|
+
});
|
|
60
|
+
const agents = specialists.map((spec) => {
|
|
61
|
+
const available = tools(spec.name);
|
|
62
|
+
return new LlmAgent({
|
|
63
|
+
name: spec.name,
|
|
64
|
+
description: spec.description,
|
|
65
|
+
model: options.model,
|
|
66
|
+
instruction: specialistInstruction(spec),
|
|
67
|
+
tools: spec.tools.map((name) => available[name]),
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
const orchestrator = new LlmAgent({
|
|
71
|
+
name: 'orchestrator',
|
|
72
|
+
model: options.model,
|
|
73
|
+
instruction: orchestratorInstruction(specialists),
|
|
74
|
+
tools: agents.map((agent) => new AgentTool({ agent })),
|
|
75
|
+
});
|
|
76
|
+
return orchestrator;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
startSession,
|
|
80
|
+
/**
|
|
81
|
+
* Runs one investigation; resolves with the final report and every tool call made on the way.
|
|
82
|
+
* Pass the sessionId from a previous answer to continue that conversation. Safe to call concurrently (per session: one at a time).
|
|
83
|
+
*/
|
|
84
|
+
async ask(question, call = {}) {
|
|
85
|
+
const sessionId = await startSession(call.sessionId);
|
|
86
|
+
const steps = [];
|
|
87
|
+
const record = (step) => {
|
|
88
|
+
steps.push(step);
|
|
89
|
+
options.onStep?.(step);
|
|
90
|
+
call.onStep?.(step);
|
|
91
|
+
};
|
|
92
|
+
const runner = new Runner({ agent: buildOrchestrator(record, (step, result) => call.onResult?.(step, result)), appName: APP_NAME, sessionService: sessions });
|
|
93
|
+
const transcript = [];
|
|
94
|
+
let text = '';
|
|
95
|
+
let failure;
|
|
96
|
+
for await (const event of runner.runAsync({ userId: USER_ID, sessionId, newMessage: { role: 'user', parts: [{ text: question }] } })) {
|
|
97
|
+
if (event.errorMessage)
|
|
98
|
+
failure = event.errorMessage;
|
|
99
|
+
if (event.author !== 'orchestrator')
|
|
100
|
+
continue;
|
|
101
|
+
for (const part of event.content?.parts ?? []) {
|
|
102
|
+
if (part.functionCall) {
|
|
103
|
+
const step = { agent: 'orchestrator', tool: part.functionCall.name ?? 'unknown', args: (part.functionCall.args ?? {}) };
|
|
104
|
+
record(step);
|
|
105
|
+
transcript.push({ kind: 'call', agent: step.tool, content: JSON.stringify(step.args) });
|
|
106
|
+
}
|
|
107
|
+
if (part.functionResponse) {
|
|
108
|
+
transcript.push({ kind: 'result', agent: part.functionResponse.name ?? 'unknown', content: JSON.stringify(part.functionResponse.response ?? {}) });
|
|
109
|
+
}
|
|
110
|
+
if (part.text) {
|
|
111
|
+
text = part.text;
|
|
112
|
+
transcript.push({ kind: 'text', agent: 'orchestrator', content: part.text });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (isLeakedToolCall(text)) {
|
|
117
|
+
return { text: '', sessionId, steps, transcript, error: 'the agents did not finish the report (a tool call leaked instead) — the evidence cards above are still valid; ask again to continue' };
|
|
118
|
+
}
|
|
119
|
+
return { text, sessionId, steps, transcript, ...(failure ? { error: failure } : {}) };
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=investigator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"investigator.js","sourceRoot":"","sources":["../../src/agents/investigator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BaseLlm, type BaseLlmConnection, type LlmRequest, type LlmResponse } from '@google/adk';
|
|
2
|
+
export declare const MOCK_PREFIX = "[mock]";
|
|
3
|
+
export declare class MockLlm extends BaseLlm {
|
|
4
|
+
constructor();
|
|
5
|
+
generateContentAsync(request: LlmRequest): AsyncGenerator<LlmResponse, void>;
|
|
6
|
+
connect(): Promise<BaseLlmConnection>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Deterministic stand-in for a real model: exercises the whole agent pipeline (orchestrator → specialists → tools)
|
|
2
|
+
// without network or tokens. Each turn it calls the next tool it hasn't called yet (one call per turn, like Kitana);
|
|
3
|
+
// when none are left it answers with a "[mock]" summary of the tool results it saw.
|
|
4
|
+
import { BaseLlm } from '@google/adk';
|
|
5
|
+
export const MOCK_PREFIX = '[mock]';
|
|
6
|
+
function questionOf(request) {
|
|
7
|
+
for (const content of [...request.contents].reverse()) {
|
|
8
|
+
const text = content.parts?.find((p) => p.text)?.text;
|
|
9
|
+
if (content.role === 'user' && text)
|
|
10
|
+
return text;
|
|
11
|
+
}
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
export class MockLlm extends BaseLlm {
|
|
15
|
+
constructor() {
|
|
16
|
+
super({ model: 'mock' });
|
|
17
|
+
}
|
|
18
|
+
async *generateContentAsync(request) {
|
|
19
|
+
// Only the current turn counts: with a session, earlier questions and their tool calls are in the history too.
|
|
20
|
+
const turnStart = request.contents.findLastIndex((c) => c.role === 'user' && c.parts?.some((p) => p.text));
|
|
21
|
+
const parts = request.contents.slice(Math.max(turnStart, 0)).flatMap((c) => c.parts ?? []);
|
|
22
|
+
const called = new Set(parts.map((p) => p.functionCall?.name).filter(Boolean));
|
|
23
|
+
const question = questionOf(request);
|
|
24
|
+
for (const tool of Object.values(request.toolsDict ?? {})) {
|
|
25
|
+
if (called.has(tool.name))
|
|
26
|
+
continue;
|
|
27
|
+
const declaration = tool._getDeclaration();
|
|
28
|
+
const required = declaration?.parameters?.required ?? declaration?.parametersJsonSchema?.required ?? [];
|
|
29
|
+
// Agent-as-tool takes the question as `request`; tools needing other required args (e.g. traceId) are skipped.
|
|
30
|
+
if (required.some((name) => name !== 'request'))
|
|
31
|
+
continue;
|
|
32
|
+
const args = required.includes('request') ? { request: question } : {};
|
|
33
|
+
yield { content: { role: 'model', parts: [{ functionCall: { name: tool.name, args } }] }, turnComplete: true };
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const results = parts
|
|
37
|
+
.filter((p) => p.functionResponse)
|
|
38
|
+
.map((p) => `${p.functionResponse.name}: ${JSON.stringify(p.functionResponse.response).slice(0, 400)}`);
|
|
39
|
+
const text = `${MOCK_PREFIX} ${results.length ? results.join(' | ') : `no tools; question was: ${question}`}`;
|
|
40
|
+
yield { content: { role: 'model', parts: [{ text }] }, turnComplete: true };
|
|
41
|
+
}
|
|
42
|
+
async connect() {
|
|
43
|
+
throw new Error('MockLlm does not support live connections');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=mock-llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-llm.js","sourceRoot":"","sources":["../../src/agents/mock-llm.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BaseLlm } from '@google/adk';
|
|
2
|
+
export type AiMode = 'mock' | 'real';
|
|
3
|
+
export type Env = Record<string, string | undefined>;
|
|
4
|
+
export declare function resolveAiMode(env: Env): AiMode;
|
|
5
|
+
export declare function getModel(env?: Env): Promise<BaseLlm | string>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { MockLlm } from './mock-llm.js';
|
|
2
|
+
export function resolveAiMode(env) {
|
|
3
|
+
const mode = env.OBSERVE_AI ?? 'mock';
|
|
4
|
+
if (mode !== 'mock' && mode !== 'real')
|
|
5
|
+
throw new Error(`OBSERVE_AI must be "mock" or "real", got "${mode}"`);
|
|
6
|
+
return mode;
|
|
7
|
+
}
|
|
8
|
+
export async function getModel(env = process.env) {
|
|
9
|
+
if (resolveAiMode(env) === 'mock')
|
|
10
|
+
return new MockLlm();
|
|
11
|
+
if (env.GEMINI_API_KEY) {
|
|
12
|
+
if (!env.GEMINI_MODEL)
|
|
13
|
+
throw new Error('GEMINI_API_KEY is set but GEMINI_MODEL is not — set the Gemini model name to use');
|
|
14
|
+
return env.GEMINI_MODEL; // ADK resolves gemini-* names and reads GEMINI_API_KEY itself
|
|
15
|
+
}
|
|
16
|
+
// Optional peer dependency: only needed when running real agents without a Gemini key.
|
|
17
|
+
const { KitanaLlm } = await import('@kitana-sdk/adk');
|
|
18
|
+
return new KitanaLlm({ model: env.OBSERVE_KITANA_MODEL ?? 'auto' });
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../src/agents/model.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAKvC,MAAM,UAAU,aAAa,CAAC,GAAQ;IACpC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,IAAI,MAAM,CAAA;IACrC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,IAAI,GAAG,CAAC,CAAA;IAC7G,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,GAAG,GAAQ,OAAO,CAAC,GAAG;IACnD,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,MAAM;QAAE,OAAO,IAAI,OAAO,EAAE,CAAA;IACvD,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,YAAY;YAAE,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAA;QAC1H,OAAO,GAAG,CAAC,YAAY,CAAA,CAAC,8DAA8D;IACxF,CAAC;IACD,uFAAuF;IACvF,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAA;IACrD,OAAO,IAAI,SAAS,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,oBAAoB,IAAI,MAAM,EAAE,CAAC,CAAA;AACrE,CAAC"}
|