Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/ai-observability/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ Each app exists to test one thing the others don't:
- `openai-agents/python-travel-triage` — tracing processor; the SDK emits the tree
- `vercel-ai/nextjs-support-chat` — per-request identity; framework bootstrap
- `manual-capture/node-http-chat` — hand-built tree; must reuse the existing client
- `google-adk/node-weather` — framework plugin; identity comes from ADK's own ids

The four weather apps implement the identical `get_weather` round trip from
The five weather apps implement the identical `get_weather` round trip from
[Anthropic's tool-use docs](https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview#how-tool-use-works),
so a diff between any two isolates one variable: the SDK, the language, or the
conversation structure.
Expand All @@ -37,7 +38,7 @@ conversation structure.
`posthog_trace_id`, and `posthog_properties`, as shown in the docs. OTel is
acceptable only where the same structure lands. Exceptions:
`openai-agents` (tracing processor), `vercel-ai` (`experimental_telemetry`),
`manual-capture` (no SDK to wrap).
`google-adk` (Runner plugin), `manual-capture` (no SDK to wrap).
- **Every app gets a session** — single-trace apps included. The graded
property is **cardinality**: one id shared by the traces that belong
together. A fresh id per call groups nothing and is worse than none.
Expand Down
2 changes: 2 additions & 0 deletions apps/ai-observability/google-adk/node-weather/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
POSTHOG_API_KEY=phc_VMTfZD5shhF3SQfgXeu6SW85FTxDMnmB4JpRbUj9QEA
POSTHOG_HOST=https://us.i.posthog.com
37 changes: 37 additions & 0 deletions apps/ai-observability/google-adk/node-weather/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# wb-aio-google-adk-node-weather

Weather assistant, Google Agent Development Kit (`@google/adk`), two-turn
conversation with a registered tool. PostHog-less fixture for
`wizard ai-observability`.

The expected mechanism is `PostHogADKPlugin` from `@posthog/ai/adk`, added to
the `Runner`'s `plugins`. The plugin captures one `$ai_generation` per model
call and takes identity from ADK itself: the run's `userId` becomes the
distinct ID, the ADK `sessionId` becomes `$ai_session_id`, and each invocation
becomes a trace. No per-call PostHog parameters exist to add.

```
thread_abc ← $ai_session_id (ADK sessionId)
├─ ask("weather in San Francisco?") ← trace (invocation)
│ ├─ model call (→ get_weather call) ← generation
│ └─ model call (→ answer) ← generation
└─ ask("How about Boston?") ← trace (same shape)
```

ADK runs the tool loop itself, and the plugin does not emit `$ai_span` events
for tool runs; the tool call is visible in the first generation's output. Do
not grade a missing tool span as a failure here.

## Expected outcome

- one session (`thread_abc`), two traces of `generation → generation`, all on
`user_123`
- `PostHogADKPlugin` in the `Runner`'s `plugins`; no wrapper client swapped in
- identity left to the plugin's ADK fallbacks, or wired explicitly to the same
`userId` / `sessionId` values; either way one session across both turns
- flushed before exit (`posthog.shutdown()`)
- `weather.ts`, the tool registration, and the agent untouched

Fail: swapping the model for a wrapped Gemini client (loses the agent
structure); a session or trace id minted per model call; hand-authored spans
around the tool; no flush.
19 changes: 19 additions & 0 deletions apps/ai-observability/google-adk/node-weather/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "wb-aio-google-adk-node-weather",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "PostHog-less weather assistant (Google ADK, tool use) for testing `wizard ai-observability`.",
"scripts": {
"build": "tsc --noEmit",
"start": "tsx src/index.ts"
},
"dependencies": {
"@google/adk": "^2.0.0",
"zod": "^4.2.1"
},
"devDependencies": {
"tsx": "^4.19.2",
"typescript": "^5.6.3"
}
}
53 changes: 53 additions & 0 deletions apps/ai-observability/google-adk/node-weather/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { FunctionTool, InMemorySessionService, LlmAgent, Runner } from '@google/adk'
import { z } from 'zod'

import { getWeather } from './weather.js'

const APP_NAME = 'wb-aio-google-adk-node-weather'
const USER_ID = 'user_123'
const SESSION_ID = 'thread_abc'

const weatherTool = new FunctionTool({
name: 'get_weather',
description: 'Get the current weather for a given location.',
parameters: z.object({
location: z.string().describe('City and state, e.g. San Francisco, CA'),
}),
execute: ({ location }) => getWeather(location),
})

const agent = new LlmAgent({
name: 'weather_assistant',
model: 'gemini-2.5-flash',
instruction: 'Answer weather questions with the get_weather tool. Be concise.',
tools: [weatherTool],
})

const sessionService = new InMemorySessionService()
const runner = new Runner({ appName: APP_NAME, agent, sessionService })

/** Answer one question inside the shared session. ADK runs the tool loop itself. */
async function ask(question: string): Promise<void> {
for await (const event of runner.runAsync({
userId: USER_ID,
sessionId: SESSION_ID,
newMessage: { role: 'user', parts: [{ text: question }] },
})) {
for (const part of event.content?.parts ?? []) {
if (part.text) {
console.log(part.text)
}
}
}
}

async function main(): Promise<void> {
await sessionService.createSession({ appName: APP_NAME, userId: USER_ID, sessionId: SESSION_ID })
await ask("What's the weather in San Francisco?")
await ask('How about Boston?')
}

main().catch((err) => {
console.error(`fatal: ${String(err)}`)
process.exit(1)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Backing implementation for the get_weather tool. No model call involved.
const forecast: Record<string, string> = {
'San Francisco, CA': '15 degrees Celsius, partly cloudy',
'Boston, MA': '4 degrees Celsius, snow showers',
}

export function getWeather(location: string): string {
return forecast[location] ?? `No forecast on file for ${location}.`
}
12 changes: 12 additions & 0 deletions apps/ai-observability/google-adk/node-weather/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["src"]
}
Loading