Skip to content
Draft
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
Binary file not shown.
246 changes: 172 additions & 74 deletions tutorials/signals-google-adk-agent/build-agent.md

Large diffs are not rendered by default.

46 changes: 11 additions & 35 deletions tutorials/signals-google-adk-agent/conclusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ title: "Conclusion and next steps"
sidebar_label: "Conclusion"
position: 6
description: "Run the full stack, debug common issues, and explore extensions like interventions, richer attributes, generative UI, multi-agent routing, and Vertex AI deployment."
keywords: ["debugging", "interventions", "generative UI", "multi-agent", "Vertex AI Agent Engine"]
date: "2026-04-17"
keywords: ["debugging", "interventions", "agentic context", "generative UI", "multi-agent", "Vertex AI Agent Engine"]
date: "2026-07-31"
---

In this tutorial, you've built a Next.js app with a Google ADK agent that uses Snowplow Signals to deliver personalized, context-aware responses based on live user behavior.
Expand All @@ -14,42 +14,18 @@ Here's what you set up:
* Snowplow Browser tracker capturing page views, page pings, and link clicks
* A Signals attribute group computing real-time session-level attributes
* A Signals service exposing those attributes via API
* A Signals agentic context buffering the session's recent events as an LLM-ready narrative
* A CopilotKit sidebar that passes the Snowplow session ID with every request
* A Google ADK agent that fetches and injects those attributes into its system prompt
* A Google ADK agent whose `before_model_callback` fetches both kinds of context and injects them into its system instruction each turn

Here are some next steps ideas for extending what you've built.
## Next steps

## Interventions

Signals also includes [interventions](/docs/signals/concepts/#interventions). These are push-based triggers that fire when a user crosses a behavioral threshold.

Rather than waiting for the user to open the chat, you can proactively provide context to your agent when something significant happens. For example, a user who has viewed pricing five times without converting. Combine this with CopilotKit's `useCopilotChatSuggestions` to surface contextual prompts in the sidebar.

Try exploring how you could use interventions within this application.

## Richer attributes

The Basic Web attribute group template covers session-level behavior. For further personalization, try extending your attribute group with:
- **Product affinity**: count of views per product category to understand user interest
- **Engagement score**: a computed signal of session depth and intent
- **Return visitor flag**: whether this is a new or returning user
- **Funnel stage**: where the user is in a defined conversion journey

## Generative UI

CopilotKit's `useCopilotAction` lets the agent render React components instead of plain text. Combine it with Signals attributes to build contextual UI. For example, a pricing comparison card that only renders for users Signals has flagged as high-intent enterprise browsers.

## Multi-agent routing

Google ADK supports `SequentialAgent`, `ParallelAgent`, and `LoopAgent` for composing multi-step workflows. You can route users to different sub-agents based on their Signals profile. For example, a support specialist for confused new users, or a technical deep-dive agent for developers in the docs.

## Multi-dimensional context

The injection happens in a plain Python callback, so you can pull context from as many sources as you need. Combine Signals real-time attributes with batch data from your warehouse. This could include attributes such as user profile data, CRM attributes, or product usage history.

## Deploy to Vertex AI Agent Engine

The ADK integration supports deployment to [Vertex AI Agent Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview), Google's managed runtime for ADK agents. Set `GOOGLE_GENAI_USE_VERTEXAI=True`, swap your API key for a service account, and deploy.
- **Interventions**: Signals also includes [interventions](/docs/signals/concepts/#interventions), push-based triggers that fire when a user crosses a behavioral threshold. Rather than waiting for the user to open the chat, you can proactively provide context to your agent when something significant happens, such as a user who has viewed pricing five times without converting. Combine this with CopilotKit's `useCopilotChatSuggestions` to surface contextual prompts in the sidebar.
- **Richer attributes**: the Basic Web template covers session-level behavior. Extend your attribute group with product affinity (views per category), an engagement score, a return visitor flag, or a funnel stage.
- **Generative UI**: CopilotKit's `useCopilotAction` lets the agent render React components instead of plain text. Combine it with Signals attributes to build contextual UI, such as a pricing comparison card that renders only for users Signals has flagged as high-intent enterprise browsers.
- **Multi-agent routing**: Google ADK supports `SequentialAgent`, `ParallelAgent`, and `LoopAgent` for composing multi-step workflows, so you can route users to different sub-agents based on their Signals profile.
- **Multi-dimensional context**: the injection happens in a plain Python callback, so you can pull context from as many sources as you need. Combine Signals real-time attributes with batch data from your warehouse, such as user profile data, CRM attributes, or product usage history.
- **Deploy to Vertex AI Agent Engine**: the ADK integration supports deployment to [Vertex AI Agent Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview), Google's managed runtime for ADK agents. Set `GOOGLE_GENAI_USE_VERTEXAI=True`, swap your API key for a service account, and deploy.

## Other Signals tutorials

Expand Down
184 changes: 156 additions & 28 deletions tutorials/signals-google-adk-agent/configure-signals.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,77 @@
---
title: "Configure Snowplow Signals"
title: "Configure Signals attributes and an agentic context"
sidebar_label: "Configure Signals"
position: 4
description: "Create an attribute group from the Basic Web template, publish it, and expose the attributes through a Signals service for lookup by session ID."
keywords: ["Signals", "attribute group", "service", "Basic Web", "domain_sessionid"]
date: "2026-04-17"
description: "Create an attribute group, a service, and an agentic context to serve real-time profile attributes and session activity to your Google ADK agent."
keywords: ["Signals", "attribute group", "service", "agentic context", "Basic Web", "domain_sessionid"]
date: "2026-07-31"
---

The next step is to define the user attributes you want to compute. You'll do this within [Snowplow Console](https://console.snowplowanalytics.com).
```mdx-code-block
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
```

The next step is to define the real-time context you want Signals to serve. You'll set up two complementary resources:

* An [attribute group](/docs/signals/concepts/#attribute-groups) and [service](/docs/signals/concepts/#services) that compute and serve profile attributes: aggregate metrics that describe the session, such as how many pages the user has viewed
* An [agentic context](/docs/signals/agentic-contexts/) that captures recent session activity: the user's latest events, readable as an LLM-ready narrative

The two work together. Attributes describe the session in aggregate, while the agentic context records what the user has just been doing, event by event. Both are scoped to the same `domain_sessionid` attribute key, so your agent can fetch both with the session ID it already reads from the tracker cookie.

## Ask the Snowplow Assistant

You can create all three resources by asking an AI assistant: the [Snowplow Assistant](/docs/llms-support/console-agent/) in Console, or your own assistant connected to the [Snowplow MCP server](/docs/llms-support/snowplow-mcp/), which you can install with `npx plugins add snowplow/skills`. Paste this prompt:

```text
In Signals, create and publish an attribute group from the Basic Web template with
domain_sessionid as the attribute key, and a service called web_agent_context that serves
it. Then create and publish an agentic context called web_agent_activity, also keyed on
domain_sessionid, buffering the last 50 page_view events from the last 30 minutes and
keeping the event_name, page_urlpath, and page_title properties. Set its prompt to: "You
are a helpful assistant for Signal Shop. Use this recent activity to understand what the
user is exploring right now, and tailor your answers to it."
```

Use these exact names, because they match the `SNOWPLOW_SIGNALS_SERVICE_NAME` and `SNOWPLOW_SIGNALS_AGENTIC_CONTEXT_NAME` variables you configured in your `.env` file. To set the same resources up by hand instead, work through the sections below.

## Create a Basic Web attribute group

Use one of Signals' built-in [attribute group](/docs/signals/concepts/#attribute-groups) templates to define attributes. Use the `domain_sessionid` as attribute key to compute session-level attributes.
Use one of Signals' built-in [attribute group](/docs/signals/concepts/#attribute-groups) templates to define attributes, with `domain_sessionid` as the attribute key so the attributes are scoped to a session.

1. In [Console](https://console.snowplowanalytics.com), navigate to **Signals** > **Attribute Groups**
2. Click **Create attribute group** and choose **Basic Web**
3. Set the **Attribute Key** to `domain_sessionid`
1. In [Console](https://console.snowplowanalytics.com), navigate to **Signals** > **Attribute groups**
2. Click **Create attribute group**, then click **Use** on the **Basic Web** card
3. Set the **Attribute key** to `domain_sessionid`

The Basic Web template includes these attributes:
Applying the template fills in the name `basic_web` and its four attributes. Rename it if you like, and leave **Source** as **Stream** so Signals computes from the event stream in real time.

| Attribute | Description |
| ----------------------- | ------------------------------------------ |
| `page_views_count` | Total number of page views in the session |
| `unique_pages_viewed` | List of unique URLs visited in the session |
| `first_event_timestamp` | When the session started |
| `last_event_timestamp` | When the most recent event was recorded |
| Attribute | Type | Aggregation | Property | Description |
| ----------------------- | ------------- | ------------- | ----------------------- | ---------------------------------------------- |
| `page_views_count` | `int32` | `counter` | none | Total number of page views in the session |
| `unique_pages_viewed` | `string_list` | `unique_list` | atomic `page_url` | Full URLs of the pages visited in the session |
| `first_event_timestamp` | `string` | `first` | atomic `derived_tstamp` | When the session started |
| `last_event_timestamp` | `string` | `last` | atomic `derived_tstamp` | When the most recent event was recorded |

Test your attribute group by clicking **Run Preview** before saving, to verify it's computing correctly based on recent events in your pipeline. This runs a query against your event data in your data warehouse and shows the computed attributes for recent sessions.
Note the property behind `unique_pages_viewed`: the template reads `page_url`, so the values are full URLs rather than paths. The agentic context you define later reads `page_urlpath` instead, which is worth knowing when you compare the two in your agent's instruction.

Click **Create attribute group** when you're happy with the attribute group.
Click **Create attribute group** when you're happy with it.

## Publish the attribute group

[Attribute groups](/docs/signals/concepts/#attribute-groups) need to be published before Signals will start computing:
[Attribute groups](/docs/signals/concepts/#attribute-groups) need to be published before Signals will start computing. A new group starts as `v1 (Not published)`:

1. Open your attribute group and click **Publish**
2. Confirm the publish to deploy the computation logic to the pipeline
2. Confirm the publish to deploy the group and its four attributes to the Profiles Store

Once published, Signals starts computing attributes for each user session as events arrive.
The version label changes to `v1 (Published)`, and Signals starts computing attributes for each user session as events arrive.

## Create a service

A [service](/docs/signals/concepts/#services) provides a pull-based API endpoint that exposes your computed attributes for a specific attribute (lookup) key.

Services allow you to combine multiple attribute groups if needed, but for this tutorial, use just the one you created in the last step.

Use this exact service name. It's the same as the `SNOWPLOW_SIGNALS_SERVICE_NAME` environment variable you configured in your `.env` file.
Use this exact service name. It's the same as the `SNOWPLOW_SIGNALS_SERVICE_NAME` environment variable you configured in your `.env` file. Service names take letters, numbers, and underscores only.

1. Navigate to **Signals** > **Services**
2. Click **Create service**
Expand All @@ -54,21 +80,123 @@ Use this exact service name. It's the same as the `SNOWPLOW_SIGNALS_SERVICE_NAME
- **Attribute groups**: Select the attribute group you just published
4. Click **Create service**

The **Attribute groups** picker only lists published attribute groups, so publishing first isn't optional. If your group is still a draft, the picker reports no options.

The service returns attributes for a given session ID in this format:

```json
{
"page_views_count": 12,
"first_event_timestamp": "2026-07-30T14:02:53.477Z",
"last_event_timestamp": "2026-07-30T14:03:12.840Z",
"page_views_count": 6,
"unique_pages_viewed": [
"http://localhost:3000/",
"http://localhost:3000/products/electronics",
"http://localhost:3000/products/clothing/linen-overshirt",
"http://localhost:3000/products/electronics/wireless-headphones",
"http://localhost:3000/products/electronics/smart-speaker-mini",
"http://localhost:3000/pricing"
],
"first_event_timestamp": "2026-04-09T14:23:01.000Z",
"last_event_timestamp": "2026-04-09T14:41:03.000Z"
]
}
```

The `unique_pages_viewed` attribute is a list of URLs the user has visited during the session, showing the agent which pages they have been browsing.
In this six-page session, six page views produce five entries in `unique_pages_viewed`, because one product page was visited twice.

## Create an agentic context

The service you just created serves computed aggregates. To also give your agent a chronological record of what the user is doing, [define an agentic context](/docs/signals/agentic-contexts/): a rolling record of the user's recent events that Signals can return as a plain-language narrative, ready to drop into your agent's instruction.

For this app, capture page view events, keeping three properties from each one:

| Property | Purpose |
| -------------- | ---------------------------------------------------------------- |
| `event_name` | Populates the event column of the narrative table |
| `page_urlpath` | Populates the URL column of the narrative table |
| `page_title` | Extra detail, included in the narrative's `event_context` column |

The `event_name` and `page_urlpath` atomic properties feed the narrative's dedicated columns. Any other property you select appears in its `event_context` column.

Your app also tracks page pings and link clicks. Leave the page pings out: the buffer holds a limited number of events, and heartbeat pings would crowd out the meaningful activity. Link clicks are worth adding once you've seen the narrative working.

Use this exact agentic context name. It's the same as the `SNOWPLOW_SIGNALS_AGENTIC_CONTEXT_NAME` environment variable you configured in your `.env` file.

<Tabs groupId="signals-impl" queryString>
<TabItem value="console" label="Console" default>

Navigate to **Signals** > **Agentic contexts** and click **Create context**. The form is one scrolling page with four sections: **Details**, **Prompt**, **Lookback Window**, and **Events and Properties**.

Fill in **Details** and **Prompt**:

| Field | Value |
| ----- | ----- |
| Name | `web_agent_activity` |
| Primary owner | Your email address, filled in for you and read-only |
| Description | `Recent session activity for the Signal Shop support agent` |
| Prompt | `You are a helpful assistant for Signal Shop. Use this recent activity to understand what the user is exploring right now, and tailor your answers to it.` |

![The Create context form in Snowplow Console, with the Details section holding the name web_agent_activity, a greyed-out Primary owner field, and the description Recent session activity for the Signal Shop support agent, and the Prompt section below it holding the Signal Shop instructions under the helper text about adding them on top of the retrieved agentic context](./images/agentic-context-create-form.png)

Under **Lookback Window**, set **Max events** to `50` and **Max age** to `30` minutes. Console restates the window underneath the fields, so you can check it reads as the last 50 events within 30 minutes. The details page later shows the same setting as **Max Age (seconds)** `1800`.

Under **Events and Properties**, click **Add event** and choose `page_view` on the **Data structures** tab. The version fills in as `1-0-0`. Then use **Add property** three times to attach `event_name`, `page_urlpath`, and `page_title` from the **Atomic** tab.

Click **Create**. Your agentic context now has a details page showing **Status** **Draft**. Click **Publish** there and confirm, and the status changes to **Published**. Later changes go through **Edit** and start as a draft, so the published version stays live while you work on it.

</TabItem>
<TabItem value="sdk" label="Python SDK">

You can also define agentic contexts programmatically with the [Signals Python SDK](https://pypi.org/project/snowplow-signals/), where the `EventLog` class is the building block. You already added this SDK to the agent's dependencies during project setup.

Start by [connecting to Signals](/docs/signals/connection/) to create a `Signals` object called `sp_signals`, then define the agentic context:

```python
from snowplow_signals import (
EventLog,
EventSelection,
EventLogEvent,
EventLogAtomicProperty,
domain_sessionid,
)

web_agent_activity = EventLog(
name="web_agent_activity",
description="Recent session activity for the Signal Shop support agent",
owner="user@company.com",
prompt=(
"You are a helpful assistant for Signal Shop. "
"Use this recent activity to understand what the user is exploring "
"right now, and tailor your answers to it."
),
attribute_key=domain_sessionid,
max_events=50,
max_age_seconds=1800,
events=[
EventSelection(
event=EventLogEvent(
name="page_view",
vendor="com.snowplowanalytics.snowplow",
version="1-0-0",
),
properties=[
EventLogAtomicProperty(name="event_name"),
EventLogAtomicProperty(name="page_urlpath"),
EventLogAtomicProperty(name="page_title"),
],
),
],
)
```

Publish it to send the configuration to your Signals infrastructure:

```python
sp_signals.publish([web_agent_activity])
```

Run this as a one-off script, separately from the agent.

</TabItem>
</Tabs>

The `prompt` text travels with the agentic context: Signals hands it to your agent alongside the captured activity, so you can refine the instructions later without touching your agent code.

With the attribute group, service, and agentic context all published, you're ready to wire them into the agent.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading