Skip to content
Merged
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
2 changes: 2 additions & 0 deletions apps/ai-observability/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ 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
- `opentelemetry/go-weather` — Go; no wrapper SDK exists, so the posthog-go OTel bridge

The five weather apps implement the identical `get_weather` round trip from
Expand All @@ -38,6 +39,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`),
`google-adk` (Runner plugin), `manual-capture` (no SDK to wrap).
`manual-capture` (no SDK to wrap),
`opentelemetry/go-weather` (Go has no wrapper SDK; the posthog-go OTel bridge).
- **Every app gets a session** — single-trace apps included. The graded
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
39 changes: 39 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,39 @@
# 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 hooks the run, agent, tool, and model
callbacks and captures the whole tree itself: an `$ai_trace` per invocation,
`$ai_span` events for agent runs and tool calls, and one `$ai_generation` per
model call. Identity comes from ADK too: the run's `userId` becomes the
distinct ID, the ADK `sessionId` becomes `$ai_session_id`. No per-call
PostHog parameters exist to add.

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

## Expected outcome

- one session (`thread_abc`), two traces of
`agent span → generation → span(get_weather) → generation`, all on `user_123`
- `PostHogADKPlugin` in the `Runner`'s `plugins`; no wrapper client swapped in
- the agent and tool spans come from the plugin's callbacks, not hand-authored
`$ai_span` capture around `getWeather`
- 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); hand-authored spans duplicating what the plugin emits; a session
or trace id minted per model call; 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-3.6-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