diff --git a/docs/integrations/assets/datadog-observability.png b/docs/integrations/assets/datadog-observability.png new file mode 100644 index 0000000000..b0c7672e5b Binary files /dev/null and b/docs/integrations/assets/datadog-observability.png differ diff --git a/docs/integrations/assets/datadog.png b/docs/integrations/assets/datadog.png new file mode 100644 index 0000000000..bc5d9e1f96 Binary files /dev/null and b/docs/integrations/assets/datadog.png differ diff --git a/docs/integrations/datadog.md b/docs/integrations/datadog.md new file mode 100644 index 0000000000..004d55a482 --- /dev/null +++ b/docs/integrations/datadog.md @@ -0,0 +1,97 @@ +--- +catalog_title: Datadog +catalog_description: Develop, evaluate, and monitor LLM applications +catalog_icon: /integrations/assets/datadog.png +catalog_tags: ["observability"] +--- + +# Datadog Observability for ADK + +
+ Supported in: + Python +
+ +[Datadog LLM +Observability](https://www.datadoghq.com/product/llm-observability/) helps AI +engineers, data scientists, and application developers quickly develop, +evaluate, and monitor LLM applications. Confidently improve output quality, +performance, costs, and overall risk with structured experiments, end-to-end +tracing across AI agents, and evaluations. + +## Overview + +Datadog LLM Observability can [automatically instrument and trace your agents +built on Google +ADK](https://docs.datadoghq.com/llm_observability/instrumentation/auto_instrumentation?tab=python#google-adk), +allowing you to: + +- **Observe agent executions and interactions** - Automatically capture every + agent run, tool call, and code execution within your agents +- **Capture LLM calls and responses** made with the underlying Google GenAI SDK +- **Debug issues** by providing error rates, token usage and cost, and + out-of-the-box evaluations on your LLM calls and tool usage + +## Prerequisites + +Sign up for a [Datadog account](https://www.datadoghq.com/) if you do not have +one and [get your API +key](https://docs.datadoghq.com/account_management/api-app-keys/#api-keys). + +## Installation + +Install the required packages: + +```bash +pip install ddtrace +``` + +## Setup + +### Create an Application using the Google ADK + +If you do not have an application using the Google ADK, follow the steps in the +[ADK Getting Started Guide](https://google.github.io/adk-docs/get-started/) to +create a sample ADK agent. + +### Configure Environment Variables + +You will need to specify an ML Application name in the following environment +variables. An ML Application is a grouping of LLM Observability traces +associated with a specific LLM-based application. See [ML Application Naming +Guidelines](https://docs.datadoghq.com/llm_observability/instrumentation/sdk?tab=python#application-naming-guidelines) +for more information on limitations with ML Application names. + +```shell +export DD_API_KEY= +export DD_SITE= +export DD_LLMOBS_ENABLED=true +export DD_LLMOBS_ML_APP= +export DD_LLMOBS_AGENTLESS_ENABLED=true +export DD_APM_TRACING_ENABLED=false # Only set this if you are not using Datadog APM +``` + +These variables must be exported before running your application so the +following `ddtrace-run` command can use them, as opposed to putting them in the +agent's `.env` file. + +### Run Your Application + +Once you have configured your environment variables, you can run your +application and start observing your LLM-based applications. + +```shell +ddtrace-run adk run my_agent +``` + +## Observe + +Navigate to the [Datadog LLM Observability Traces +View](https://app.datadoghq.com/llm/traces) to see the traces generated by your +application. + +![datadog-observability.png](./assets/datadog-observability.png) + +## Support and Resources +- [Datadog LLM Observability](https://www.datadoghq.com/product/llm-observability/) +- [Datadog Support](https://docs.datadoghq.com/help/) \ No newline at end of file diff --git a/docs/observability/index.md b/docs/observability/index.md index 4ff19029ab..cc0b9843b5 100644 --- a/docs/observability/index.md +++ b/docs/observability/index.md @@ -7,12 +7,11 @@ agents, you may need these features to help debug and diagnose their in-process behavior. Basic input and output monitoring is typically insufficient for agents with any significant level of complexity. -Agent Development Kit (ADK) provides configurable -[logging](/observability/logging/) -functionality for monitoring and debugging agents. However, you may -need to consider more advanced -[observability ADK Integrations](/integrations/?topic=observability) -for monitoring and analysis. +Agent Development Kit (ADK) provides built-in observability through +[logging](/observability/logging/), [metrics](/observability/metrics/), and +[traces](/observability/traces/) to help you monitor and debug your agents. +However, you may need to consider more advanced [observability ADK +Integrations](/integrations/?topic=observability) for monitoring and analysis. !!! tip "ADK Integrations for observability" For a list of pre-built observability libraries for ADK, see diff --git a/docs/observability/logging.md b/docs/observability/logging.md index d7ee448d16..9069e8033b 100644 --- a/docs/observability/logging.md +++ b/docs/observability/logging.md @@ -61,6 +61,7 @@ The available log levels for the `--log_level` option are: | **`ERROR`** | A serious error that prevented an operation from completing. |
  • Failed API calls to external services (e.g., LLM, Session Service).
  • Unhandled exceptions during agent execution.
  • Configuration errors.
| **Note:** It is recommended to use `INFO` or `WARNING` in production environments. Only enable `DEBUG` when actively troubleshooting an issue, as `DEBUG` logs can be very verbose and may contain sensitive information. + --- ## Configuring Logging in Go @@ -91,9 +92,9 @@ import ( func main() { ctx := context.Background() - + // Initialize telemetry with prompt content logging enabled - tp, err := telemetry.New(ctx, + tp, err := telemetry.New(ctx, telemetry.WithGenAICaptureMessageContent(true), // Add other options like WithOtelToCloud(true) for GCP export ) @@ -101,10 +102,10 @@ func main() { // handle error } defer tp.Shutdown(ctx) - + // Register as global OTel providers tp.SetGlobalOtelProviders() - + // Your ADK agent code follows... } ``` @@ -144,6 +145,7 @@ By reading the logger name, you can immediately pinpoint the source of the log a **Scenario:** Your agent is not producing the expected output, and you suspect the prompt being sent to the LLM is incorrect. **Steps:** + 1. **Enable DEBUG Logging:** In your `main.py`, set the logging level to `DEBUG` as shown in the configuration example. ```python logging.basicConfig( @@ -151,8 +153,11 @@ By reading the logger name, you can immediately pinpoint the source of the log a format='%(asctime)s - %(levelname)s - %(name)s - %(message)s' ) ``` + 2. **Run Your Agent:** Execute your agent's task as you normally would. + 3. **Inspect the Logs:** Look through the console output for a message from the `google.adk.models.google_llm` logger that starts with `LLM Request:`. + ```log ... 2025-07-10 15:26:13,778 - DEBUG - google_adk.google.adk.models.google_llm - Sending out request, model: gemini-flash-latest, backend: GoogleLLMVariant.GEMINI_API, stream: False @@ -195,6 +200,7 @@ By reading the logger name, you can immediately pinpoint the source of the log a I have rolled a 6 sided die, and the result is 2. ... ``` + 4. **Analyze the Prompt:** By examining the `System Instruction`, `contents`, `functions` sections of the logged request, you can verify: - Is the system instruction correct? - Is the conversation history (`user` and `model` turns) accurate? diff --git a/docs/observability/metrics.md b/docs/observability/metrics.md new file mode 100644 index 0000000000..c424d83740 --- /dev/null +++ b/docs/observability/metrics.md @@ -0,0 +1,93 @@ +# Agent activity metrics + +
+ Supported in ADKPython v1.32.0 +
+ +Agent Development Kit (ADK) provides built-in, vendor-neutral metrics collection to help you understand the performance, cost, and usage patterns of your agents. While logs provide a detailed narrative of *what* happened, metrics give you aggregated, quantitative data to answer *how often* and *how fast* things are happening. + +## Metrics philosophy + +ADK's approach to metrics is designed to be lightweight, standardized, and entirely agnostic to your choice of monitoring backend. + +* **OpenTelemetry Semantic Conventions:** ADK implements the OpenTelemetry (OTel) [Semantic Conventions for GenAI](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-metrics.md). This ensures that metrics are recorded under standard, predictable attribute and metric names. +* **OTLP Wire Format:** ADK emits data using the standard OTLP format, ensuring that your metrics will seamlessly integrate into any OTel-compatible backend (e.g., Prometheus, Datadog, SigNoz, Google Cloud Monitoring). +* **Cost and Performance Focused:** Metrics are significantly less costly and more performant than logs or traces when performing analytics over large swathes of data. ADK tracks the most critical signals for LLM applications: token consumption, request latency, and tool execution reliability. +* **Vendor-Neutral Export:** ADK does not lock you into a specific metrics pipeline. You instantiate standard OTel meter providers and export data wherever your infrastructure demands. + +--- + +## Metrics schema + +When metrics are enabled, ADK automatically instruments the agent's lifecycle, workflow steps, and tool executions based on the OpenTelemetry GenAI Semantic Conventions. The following core metrics are emitted: + +| Metric Name | Type | Description | Key Attributes (Dimensions) | +| :--- | :--- | :--- | :--- | +| **`gen_ai.agent.invocation.duration`** | Histogram | The total time taken for an agent to process a prompt and return a response. | `gen_ai.agent.name`, `error.type` | +| **`gen_ai.tool.execution.duration`** | Histogram | The execution latency of individual tools called by the agent. Useful for spotting slow external APIs. | `gen_ai.tool.name`, `error.type` | +| **`gen_ai.agent.request.size`** | Histogram | The size or complexity of the incoming request sent to the agent. | `gen_ai.agent.name` | +| **`gen_ai.agent.response.size`** | Histogram | The size or complexity of the final response generated by the agent. | `gen_ai.agent.name` | +| **`gen_ai.agent.workflow.steps`** | Histogram | Tracks the number of iterative steps or reasoning loops an agent takes to complete a workflow. | `gen_ai.agent.name` | + +--- + +## Metrics export setup + +### Metrics export in ADK Web + +If you are running your agent using the `adk web` or `adk api_server` CLI commands, you can configure metrics export. + + +#### OTLP export + +To export metrics to an OTLP-compatible backend, set the standard OTel environment variables: + +```bash +export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="http://your-collector:4318/v1/metrics" +adk web path/to/your/agents_dir +``` + +> **Note:** You can also set the general `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable if you would like to send traces and logs to the same endpoint in addition to metrics. + +#### GCP export + +To enable metrics export to Google Cloud Monitoring, use the `-otel_to_cloud` flag: + +```bash +adk web -otel_to_cloud path/to/your/agents_dir +``` + +### Programmatic metrics export + +You can also configure metrics export programmatically in your application code. + +#### OTLP export setup + +To enable metrics and export them to an OpenTelemetry Collector (or an OTLP-compatible backend) programmatically: + +```python +from google.adk.telemetry.setup import maybe_set_otel_providers +import os + +os.environ["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] = "http://your-collector:4318/v1/metrics" +os.environ["OTEL_SERVICE_NAME"] = "your-adk-agent" +os.environ["OTEL_RESOURCE_ATTRIBUTES"] = "key1=value1,key2=value2" +maybe_set_otel_providers() +``` + +#### GCP export setup + +To export metrics to Google Cloud Monitoring programmatically, use the OpenTelemetry Google Cloud exporter. Here is an example in Python: + +```python +from google.adk.telemetry.google_cloud import get_gcp_exporters +from google.adk.telemetry.setup import maybe_set_otel_providers +import os + +gcp_exporters = get_gcp_exporters( + enable_cloud_metrics = True, +) +os.environ["OTEL_SERVICE_NAME"] = "your-adk-agent" +os.environ["OTEL_RESOURCE_ATTRIBUTES"] = "key1=value1,key2=value2" +maybe_set_otel_providers([gcp_exporters]) +``` diff --git a/docs/observability/traces.md b/docs/observability/traces.md new file mode 100644 index 0000000000..d0c6cee52f --- /dev/null +++ b/docs/observability/traces.md @@ -0,0 +1,92 @@ +# Agent activity traces + +
+ Supported in ADKPython v1.17.0Go v1.0.0 +
+ +Agent Development Kit (ADK) provides distributed tracing capabilities to help you visualize the end-to-end journey of a request as it travels through your agent's architecture. While metrics tell you *how long* a process took and logs tell you *what* happened, traces connect these events, showing you exactly *where* the time was spent and the hierarchical relationship between LLM reasoning, tool calls, and external APIs. + +## Traces philosophy + +ADK's approach to tracing is built on standard protocols to ensure seamless integration with your existing observability stack. + +* **OpenTelemetry Semantic Conventions:** ADK implements the OpenTelemetry (OTel) [Semantic Conventions for GenAI](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md). This ensures that trace spans and attributes are recorded under standard, predictable names. +* **OTLP Wire Format:** ADK emits data using the standard OTLP format, ensuring that your traces will seamlessly integrate into any OTel-compatible backend (e.g., Google Cloud Trace, Jaeger, Grafana Tempo, Datadog). +* **Hierarchical Visualization:** Traces are organized into "Spans." An agent run is a root span, which contains child spans for LLM operations, which may in turn contain child spans for tool executions. This creates a clear "waterfall" view of the agent's reasoning loop. +* **Context Propagation:** ADK automatically passes trace context across process boundaries, ensuring that if your agent calls an external microservice via a tool, that service's spans are linked to the agent's root trace. + +--- + +## Traces schema + +When tracing is enabled, ADK automatically instruments key operations following the OpenTelemetry GenAI Semantic Conventions for Agents. A typical trace waterfall includes the following spans: + +| Span Name | Type | Description | Key Attributes | +| :--- | :--- | :--- | :--- | +| **[`invoke_agent`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md#invoke-agent-client-span)** | Client / Internal Span | Describes GenAI agent invocation over a remote service or locally. Represents the lifecycle of an agent interaction.| `gen_ai.agent.name`, `gen_ai.system` | +| **[`invoke_workflow`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md#invoke-workflow-span)** | Child Span | Describes the invocation of a multi-step agentic workflow. | `gen_ai.workflow.name`, `gen_ai.system`| +| **[`execute_tool`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-agent-spans.md#execute-tool-span)** | Child Span | Represents the execution of a specific tool or function call requested by the GenAI system.| `gen_ai.tool.name`, `gen_ai.system`| +| **[`generate_content {model.name}`](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/gen-ai-spans.md)** | Internal Span | Represents the invocation of the underlying language model (via the GenAI SDK) to generate content. It tracks the request parameters, response details, and usage metrics. | `gen_ai.operation.name`, `gen_ai.system`, `gen_ai.request.model`, `gen_ai.agent.name`, `gen_ai.conversation.id`, `user.id`, `gen_ai.request.top_p`, `gen_ai.request.max_tokens`, `gen_ai.response.finish_reasons`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens` | + +--- + +## Traces export setup + +### Traces export in ADK Web + +If you are running your agent using the `adk web` or `adk api_server` CLI commands, you can configure trace exports. + +#### OTLP export + +To export traces to an OTLP-compatible backend, set the standard OTel environment variables: + +```bash +export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="http://your-collector:4318/v1/traces" +adk web path/to/your/agents_dir +``` + +> **Note:** You can also set the general `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable if you would like to send metrics and logs to the same endpoint in addition to traces. + + +#### GCP export + +To enable trace export to Google Cloud Trace, use the `-otel_to_cloud` flag: + +```bash +adk web -otel_to_cloud path/to/your/agents_dir +``` + +### Programmatic traces export + +You can also configure trace export programmatically in your application code. + +#### OTLP export setup + +To enable tracing and export spans to an OpenTelemetry Collector programmatically: + +```python +from google.adk.telemetry.setup import maybe_set_otel_providers +import os + +os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] = "http://your-collector:4318/v1/traces" +os.environ["OTEL_SERVICE_NAME"] = "your-adk-agent" +os.environ["OTEL_RESOURCE_ATTRIBUTES"] = "key1=value1,key2=value2" +maybe_set_otel_providers() +``` + +#### GCP export setup + +To export traces to Google Cloud Trace programmatically, use the OpenTelemetry Google Cloud exporter. Here is an example in Python: + +```python +from google.adk.telemetry.google_cloud import get_gcp_exporters +from google.adk.telemetry.setup import maybe_set_otel_providers +import os + +gcp_exporters = get_gcp_exporters( + enable_cloud_tracing = True, +) +os.environ["OTEL_SERVICE_NAME"] = "your-adk-agent" +os.environ["OTEL_RESOURCE_ATTRIBUTES"] = "key1=value1,key2=value2" +maybe_set_otel_providers([gcp_exporters]) +``` diff --git a/docs/sessions/memory.md b/docs/sessions/memory.md index b4fa365134..eb6faf33de 100644 --- a/docs/sessions/memory.md +++ b/docs/sessions/memory.md @@ -13,24 +13,30 @@ Think of it this way: ## The `MemoryService` Role -The `BaseMemoryService` (or `Service` in Go) defines the interface for managing this searchable, long-term knowledge store. Its primary responsibilities are: +The `BaseMemoryService` (or `Service` in Go) defines the interface for managing this searchable, long-term knowledge store. It supports four operations: -1. **Ingesting Information (`add_session_to_memory`):** Taking the contents of a (usually completed) `Session` and adding relevant information to the long-term knowledge store. -2. **Searching Information (`search_memory`):** Allowing an agent (typically via a `Tool`) to query the knowledge store and retrieve relevant snippets or context based on a search query. +1. **Ingesting a session (`add_session_to_memory`):** Take the contents of a (usually completed) `Session` and add relevant information to the long-term knowledge store. +2. **Ingesting events incrementally (`add_events_to_memory`):** Append a delta of events (e.g., the latest turn) without re-ingesting the full session. Useful when you want to write to memory partway through a long-running session. +3. **Writing memory items directly (`add_memory`):** Insert pre-built `MemoryEntry` items, for services that support direct writes alongside event-based extraction. +4. **Searching (`search_memory`):** Allow an agent (typically via a `Tool`) to query the knowledge store and retrieve relevant snippets based on a search query. + +Operations 2 and 3 are optional — the base class implementations of `add_events_to_memory` and `add_memory` raise `NotImplementedError`, so check your concrete service before relying on them. ## Choosing the Right Memory Service -The ADK offers two distinct `MemoryService` implementations, each tailored to different use cases. Use the table below to decide which is the best fit for your agent. +The Python ADK ships three `MemoryService` implementations. Use the table below to decide which is the best fit for your agent. + +| **Feature** | **InMemoryMemoryService** | **VertexAiMemoryBankService** | **VertexAiRagMemoryService** | +| :--- | :--- | :--- | :--- | +| **Persistence** | None (data is lost on restart) | Yes (Managed by Agent Platform) | Yes (stored in RAG Engine) | +| **Primary Use Case** | Prototyping, local development, and simple testing. | Building meaningful, evolving memories from user conversations. | Vector-search retrieval over the full conversation corpus, or alongside other RAG-indexed content. | +| **Memory Extraction** | Stores full conversation | Extracts [meaningful information](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/memory-bank/generate-memories) from conversations and consolidates it with existing memories (powered by LLM) | Stores full conversation, indexed by [RAG Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/rag-engine/rag-overview). | +| **Search Capability** | Basic keyword matching. | Advanced semantic search. | Vector similarity search over RAG Engine. | +| **Setup Complexity** | None. It's the default. | Low. Requires an [Agent Runtime](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/memory-bank/overview) instance on Agent Platform. | Medium. Requires [RAG Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/rag-engine/manage-your-rag-corpus). | +| **Dependencies** | None. | Google Cloud Project, Agent Platform API | Google Cloud Project, RAG Engine, the Agent Platform SDK (optional install). | +| **When to use it** | When you want to search across multiple sessions’ chat histories for prototyping. | When you want your agent to remember and learn from past interactions. | When you already have RAG infrastructure or want to retrieve over raw conversation transcripts. | -| **Feature** | **InMemoryMemoryService** | **VertexAiMemoryBankService** | -| :--- | :--- | :--- | -| **Persistence** | None (data is lost on restart) | Yes (Managed by Agent Platform) | -| **Primary Use Case** | Prototyping, local development, and simple testing. | Building meaningful, evolving memories from user conversations. | -| **Memory Extraction** | Stores full conversation | Extracts [meaningful information](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/memory-bank/generate-memories) from conversations and consolidates it with existing memories (powered by LLM) | -| **Search Capability** | Basic keyword matching. | Advanced semantic search. | -| **Setup Complexity** | None. It's the default. | Low. Requires an [Agent Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/memory-bank/overview) instance on Agent Platform. | -| **Dependencies** | None. | Google Cloud Project, Agent Platform API | -| **When to use it** | When you want to search across multiple sessions’ chat histories for prototyping. | When you want your agent to remember and learn from past interactions. | +`VertexAiRagMemoryService` is only exported from `google.adk.memory` when the Agent Platform SDK is installed. Memory Bank and RAG-backed memory are documented in [Memory Bank](#memory-bank) and [RAG Memory](#rag-memory) below. ## In-Memory Memory @@ -278,7 +284,26 @@ This example demonstrates the basic flow using the `InMemoryMemoryService` for s ### Searching Memory Within a Tool -You can also search memory from within a custom tool by using the `tool.Context`. +You can also search memory from within a custom tool by using the tool context. + +=== "Python" + + ```python + from google.adk.tools import ToolContext + + async def search_past_conversations( + query: str, tool_context: ToolContext + ) -> dict: + response = await tool_context.search_memory(query) + return { + "results": [ + part.text + for entry in response.memories + for part in (entry.content.parts or []) + if part.text + ] + } + ``` === "Go" @@ -370,6 +395,22 @@ Or, you can configure your agent to use the Memory Bank by manually instantiatin ) ``` +## RAG Memory + +The `VertexAiRagMemoryService` stores conversations in [RAG Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/rag-engine/rag-overview) and retrieves them by vector similarity. Use it when you already have RAG infrastructure or want raw transcript retrieval rather than the LLM-extracted memories produced by Memory Bank. Requires the Agent Platform SDK. + +=== "Python" + + ```py + from google.adk.memory import VertexAiRagMemoryService + + memory_service = VertexAiRagMemoryService( + rag_corpus="projects/PROJECT_ID/locations/LOCATION/ragCorpora/CORPUS_ID", + similarity_top_k=5, + vector_distance_threshold=0.6, + ) + ``` + ## Using Memory in Your Agent When a memory service is configured, your agent can use a tool or callback to retrieve memories. ADK includes two pre-built tools for retrieving memories: @@ -441,8 +482,7 @@ To extract memories from your session, you need to call `add_session_to_memory`. from google import adk async def auto_save_session_to_memory_callback(callback_context): - await callback_context._invocation_context.memory_service.add_session_to_memory( - callback_context._invocation_context.session) + await callback_context.add_session_to_memory() agent = Agent( model=MODEL, @@ -512,56 +552,59 @@ The memory workflow internally involves these steps: 2. **Ingestion into Memory:** At some point (often when a session is considered complete or has yielded significant information), your application calls `memory_service.add_session_to_memory(session)`. This extracts relevant information from the session's events and adds it to the long-term knowledge store (in-memory dictionary or Agent Runtime Memory Bank). 3. **Later Query:** In a *different* (or the same) session, the user might ask a question requiring past context (e.g., "What did we discuss about project X last week?"). 4. **Agent Uses Memory Tool:** An agent equipped with a memory-retrieval tool (like the built-in `load_memory` tool) recognizes the need for past context. It calls the tool, providing a search query (e.g., "discussion project X last week"). -5. **Search Execution:** The tool internally calls `memory_service.search_memory(app_name, user_id, query)`. -6. **Results Returned:** The `MemoryService` searches its store (using keyword matching or semantic search) and returns relevant snippets as a `SearchMemoryResponse` containing a list of `MemoryResult` objects (each potentially holding events from a relevant past session). +5. **Search Execution:** The tool internally calls `memory_service.search_memory(app_name=..., user_id=..., query=...)`. +6. **Results Returned:** The `MemoryService` searches its store (using keyword matching or semantic search) and returns matching snippets as a `SearchMemoryResponse` containing a list of `MemoryEntry` objects (each holding `content`, optional `author`, optional `timestamp`, and optional `custom_metadata`). 7. **Agent Uses Results:** The tool returns these results to the agent, usually as part of the context or function response. The agent can then use this retrieved information to formulate its final answer to the user. ### Can an agent have access to more than one memory service? -* **Through Standard Configuration: No.** The framework (`adk web`, `adk api_server`) is designed to be configured with one single memory service at a time via the `--memory_service_uri` flag. This single service is then provided to the agent and accessed through the built-in `self.search_memory()` method. From a configuration standpoint, you can only choose one backend (`InMemory`, `VertexAiMemoryBankService`) for all agents served by that process. +* **Through Standard Configuration: No.** The framework (`adk web`, `adk api_server`) is designed to be configured with one memory service at a time via the `--memory_service_uri` flag. That single service is wired into the runner and exposed through `tool_context.search_memory()` and `callback_context.search_memory()`. -* **Within Your Agent's Code: Yes, absolutely.** There is nothing preventing you from manually importing and instantiating another memory service directly inside your agent's code. This allows you to access multiple memory sources within a single agent turn. +* **Within Your Agent's Code: Yes.** Nothing stops you from importing and instantiating a second `BaseMemoryService` directly. The cleanest place to consult it is from a custom tool, which already has a `ToolContext` for the framework-configured service. -For example, your agent could use the framework-configured `InMemoryMemoryService` to recall conversational history, and also manually instantiate a `VertexAiMemoryBankService` to look up information in a technical manual. +For example, your agent can use the framework-configured `InMemoryMemoryService` for conversation history and manually instantiate a second service (a `VertexAiMemoryBankService`, a `VertexAiRagMemoryService` over a docs corpus, or any other `BaseMemoryService` implementation) for a separate knowledge base. #### Example: Using Two Memory Services -Here’s how you could implement that in your agent's code: - === "Python" -```python -from google.adk.agents import Agent -from google.adk.memory import InMemoryMemoryService, VertexAiMemoryBankService -from google.genai import types - -class MultiMemoryAgent(Agent): - def __init__(self, **kwargs): - super().__init__(**kwargs) - - self.memory_service = InMemoryMemoryService() - # Manually instantiate a second memory service for document lookups - self.vertexai_memorybank_service = VertexAiMemoryBankService( - project="PROJECT_ID", - location="LOCATION", - agent_engine_id="AGENT_ENGINE_ID" - ) + ```python + from google.adk.agents import Agent + from google.adk.memory import InMemoryMemoryService + from google.adk.tools import ToolContext - async def run(self, request: types.Content, **kwargs) -> types.Content: - user_query = request.parts[0].text + # Second memory service for docs lookup; could be any BaseMemoryService. + docs_memory = InMemoryMemoryService() - # 1. Search conversational history using the framework-provided memory - # (This would be InMemoryMemoryService if configured) - conversation_context = await self.memory_service.search_memory(query=user_query) - # 2. Search the document knowledge base using the manually created service - document_context = await self.vertexai_memorybank_service.search_memory(query=user_query) + async def search_all_memory(query: str, tool_context: ToolContext) -> dict: + """Search both the conversational memory and the docs corpus.""" + conversational = await tool_context.search_memory(query) + docs = await docs_memory.search_memory( + app_name="docs", user_id="shared", query=query + ) + return { + "from_conversations": [ + part.text + for entry in conversational.memories + for part in (entry.content.parts or []) + if part.text + ], + "from_docs": [ + part.text + for entry in docs.memories + for part in (entry.content.parts or []) + if part.text + ], + } - # Combine the context from both sources to generate a better response - prompt = "From our past conversations, I remember:\n" - prompt += f"{conversation_context.memories}\n\n" - prompt += "From the technical manuals, I found:\n" - prompt += f"{document_context.memories}\n\n" - prompt += f"Based on all this, here is my answer to '{user_query}':" - return await self.llm.generate_content_async(prompt) -``` + agent = Agent( + model="gemini-flash-latest", + name="multi_memory_agent", + instruction=( + "Answer questions using both your conversation history and the " + "docs knowledge base. Use the search_all_memory tool." + ), + tools=[search_all_memory], + ) + ``` diff --git a/docs/skills/index.md b/docs/skills/index.md index d3f5900e1b..7fba89b3d9 100644 --- a/docs/skills/index.md +++ b/docs/skills/index.md @@ -1,7 +1,7 @@ # Skills for ADK agents
- Supported in ADKPython v1.25.0TypeScript v0.6.1Experimental + Supported in ADKPython v1.25.0TypeScript v0.6.1Go v1.2.0Experimental
An agent ***Skill*** is a self-contained unit of functionality that an ADK agent @@ -12,15 +12,17 @@ The structure of a Skill allows it to be loaded incrementally to minimize the impact on the operating context window of the agent. !!! example "Experimental" - The Skills feature is experimental. We welcome your - [feedback](https://github.com/google/adk-python/issues/new?template=feature_request.md&labels=skills)! + The Skills feature is experimental. We welcome your feedback via the + respective ADK GitHub repositories: + [ADK Python](https://github.com/google/adk-python/issues/new?template=feature_request.md&labels=skills), + [ADK TypeScript](https://github.com/google/adk-js/issues/new?template=feature_request.md&labels=skills), + [ADK Go](https://github.com/google/adk-go/issues/new?template=feature_request.md&labels=skills). ## Get started -Use the `SkillToolset` class to include one or more Skills in your agent -definition and then add to your agent's tools list. You can define a -[Skill in code](#inline-skills), -or load the skill from a file definition, as shown below: +Use the `SkillToolset` class to make one or more Skills available to your agent. +You can define [skills in code](#inline-skills) or load +[skills from a filesystem](#filesystem-skills). === "Python" @@ -53,17 +55,52 @@ or load the skill from a file definition, as shown below: ) ``` + For a complete code example of an ADK agent with a Skill, including both + file-based and in-line Skill definitions, see the code sample + [skills_agent](https://github.com/google/adk-python/tree/main/contributing/samples/skills_agent). + === "TypeScript" ```typescript --8<-- "examples/typescript/snippets/skills/get_started.ts:full_example" ``` -For a complete code example of an ADK agent with a Skill, including both -file-based and in-line Skill definitions, see the code sample -[skills_agent](https://github.com/google/adk-python/tree/main/contributing/samples/skills_agent). +=== "Go" -## Define Skills + ```go + import ( + "context" + "os" + + "google.golang.org/adk/agent/llmagent" + "google.golang.org/adk/tool/skilltoolset/skill" + "google.golang.org/adk/tool/skilltoolset" + "google.golang.org/adk/tool" + ) + + mySkillToolset, err := skilltoolset.New(ctx, skilltoolset.Config{ + Source: skill.NewFileSystemSource(os.DirFS("./skills")), + }) + if err != nil { + // handle error + } + + rootAgent, err := llmagent.New(llmagent.Config{ + Name: "skill_user_agent", + Model: model, + Description: "An agent that can use specialized skills.", + Instruction: "You are a helpful assistant that can leverage skills to perform tasks.", + Toolsets: []tool.Toolset{mySkillToolset}, + }) + if err != nil { + // handle error + } + ``` + + For a complete example, see the code sample in + [skills](https://github.com/google/adk-go/tree/main/examples/skills). + +## Understand Skills The Skills feature allows you to create modular packages of Skill instructions and resources that agents can load on demand. This approach helps you organize @@ -86,20 +123,20 @@ three levels: documentation, templates, or examples. - `scripts/`: Executable scripts supported by the agent runtime. -### Define Skills with files +### Skills directory structure The following directory structure shows the recommended way to include Skills in -your ADK agent project. The `example_skill/` directory shown below, and any +your ADK agent project. The `example-skill/` directory shown below, and any parallel Skill directories, must follow the -[Agent Skill specification](https://agentskills.io/specification) -file structure. Only the `SKILL.md` file is required. +[Agent Skill specification](https://agentskills.io/specification) file +structure. Only the `SKILL.md` file is required. ``` my_agent/ - agent.py (or agent.ts) + agent.py (or agent.ts / main.go) .env skills/ - example_skill/ # Skill + example-skill/ # Skill SKILL.md # main instructions (required) references/ REFERENCE.md # detailed API reference @@ -113,10 +150,14 @@ my_agent/ *.ts # utility scripts (TypeScript) ``` +## Skill sources + +You can define [skills within the code](#inline-skills) or read +[skills from a filesystem](#filesystem-skills). + ### Define Skills in code {#inline-skills} -In ADK agents, you can also define Skills within the code of the agent, as shown below. This method of Skill definition enables -you to dynamically modify skills from your ADK agent code. +You can define Skills within the code of your agent, as shown below. === "Python" @@ -149,11 +190,133 @@ you to dynamically modify skills from your ADK agent code. --8<-- "examples/typescript/snippets/skills/inline_skill.ts:full_example" ``` +=== "Go" + + !!! note + ADK Go does not currently provide a standard Source for inline skills, + though this may be added in the future. + To define skills directly in code, you must implement the `skill.Source` + interface yourself, as shown below. + + ```go + import ( + "context" + "io" + "slices" + "strings" + + "google.golang.org/adk/tool/skilltoolset/skill" + ) + + // Example implementation of a static in-memory skill.Source: + type StaticSource struct{} + + func (s *StaticSource) ListFrontmatters(ctx context.Context) ([]*skill.Frontmatter, error) { + return []*skill.Frontmatter{ + {Name: "greeting-skill", Description: "A friendly greeting skill that can say hello to a specific person."}, + }, nil + } + + func (s *StaticSource) LoadFrontmatter(ctx context.Context, name string) (*skill.Frontmatter, error) { + if name != "greeting-skill" { + return nil, skill.ErrSkillNotFound + } + return &skill.Frontmatter{Name: "greeting-skill", Description: "A friendly greeting skill that can say hello to a specific person."}, nil + } + + func (s *StaticSource) LoadInstructions(ctx context.Context, name string) (string, error) { + if name != "greeting-skill" { + return "", skill.ErrSkillNotFound + } + return "Step 1: Read the 'references/hello_world.txt' file to understand how to greet the user. Step 2: Return a greeting based on the reference.", nil + } + + func (s *StaticSource) ListResources(ctx context.Context, name, subpath string) ([]string, error) { + if name != "greeting-skill" { + return nil, skill.ErrSkillNotFound + } + if !slices.Contains([]string{"", ".", "references", "references/"}, subpath) { + return nil, skill.ErrResourceNotFound + } + return []string{"references/hello_world.txt", "references/example.md"}, nil + } + + func (s *StaticSource) LoadResource(ctx context.Context, name, resourcePath string) (io.ReadCloser, error) { + if name != "greeting-skill" { + return nil, skill.ErrSkillNotFound + } + switch resourcePath { + case "references/hello_world.txt": + return io.NopCloser(strings.NewReader("Hello! So glad to have you here!")), nil + case "references/example.md": + return io.NopCloser(strings.NewReader("This is an example reference.")), nil + default: + return nil, skill.ErrResourceNotFound + } + } + ``` + +!!! note + The `Source` interface can be backed by any data store (such as a database) + to support dynamic use cases like live updates and personalization. + +### Read Skills from filesystem {#filesystem-skills} + +=== "Python" + + ```python + import pathlib + + from google.adk.skills import load_skill_from_dir + from google.adk.tools import skill_toolset + + greeting_skill = load_skill_from_dir( + pathlib.Path(__file__).parent / "skills" / "greeting-skill" + ) + weather_skill = load_skill_from_dir( + pathlib.Path(__file__).parent / "skills" / "weather-skill" + ) + + my_skill_toolset = skill_toolset.SkillToolset( + skills=[weather_skill, greeting_skill], + ) + ``` + +=== "Go" + + ```go + import ( + "os" + + "google.golang.org/adk/tool/skilltoolset/skill" + "google.golang.org/adk/tool/skilltoolset" + ) + + // ... + + source := skill.NewFileSystemSource(os.DirFS("./skills")) + + // This example doesn't use any optional wrappers, but you can use them if + // needed, e.g.: + // source, _, err = skill.WithFrontmatterPreloadSource(ctx, source) + // source, _, err = skill.WithCompletePreloadSource(ctx, source) + // For more information about these and other wrappers, see + // https://pkg.go.dev/google.golang.org/adk/tool/skilltoolset/skill#Source. + + skillToolset, err := skilltoolset.New(ctx, skilltoolset.Config{ + Source: source, + }) + if err != nil { + // handle error + } + ``` + + + ## Next steps Check out these resources for building agents with Skills: -* ADK Skills agent code sample: - [skills_agent](https://github.com/google/adk-python/tree/main/contributing/samples/skills_agent). -* Agent Skills - [specification documentation](https://agentskills.io/) +- [Skills in Python - code sample](https://github.com/google/adk-python/tree/main/contributing/samples/skills_agent) +- [Skills in Go - code sample](https://github.com/google/adk-go/tree/main/examples/skills) +- Agent Skills [specification documentation](https://agentskills.io/) diff --git a/examples/python/snippets/tools/overview/toolset_example.py b/examples/python/snippets/tools/overview/toolset_example.py index 74b9032833..3ea7930213 100644 --- a/examples/python/snippets/tools/overview/toolset_example.py +++ b/examples/python/snippets/tools/overview/toolset_example.py @@ -12,17 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +## --8<-- [start:init] + import asyncio from typing import Optional, List, Dict, Any -from google.adk.agents import LlmAgent, ReadonlyContext -from google.adk.tools import BaseTool, FunctionTool, BaseToolset -from google.adk.tools.tool_context import ToolContext # For tool implementation -from google.adk.runners import Runner # For conceptual run -from google.adk.sessions import InMemorySessionService # For conceptual run -from google.genai.types import Content, Part - -## --8<-- [start:init] +from google.adk.agents import LlmAgent +from google.adk.agents.readonly_context import ReadonlyContext +from google.adk.tools import BaseTool, FunctionTool +from google.adk.tools.base_toolset import BaseToolset +from google.adk.tools.tool_context import ToolContext +from google.adk.runners import InMemoryRunner # 1. Define the individual tool functions @@ -55,22 +55,23 @@ def subtract_numbers(a: int, b: int) -> Dict[str, Any]: # 2. Create the Toolset by implementing BaseToolset class SimpleMathToolset(BaseToolset): - def __init__(self, prefix: str = "math_"): + def __init__(self, prefix: str = "math"): self.prefix = prefix + super().__init__(tool_name_prefix=self.prefix) # Toolset can customize names by passing a prefix + # Create FunctionTool instances once self._add_tool = FunctionTool( func=add_numbers, - name=f"{self.prefix}add_numbers", # Toolset can customize names ) self._subtract_tool = FunctionTool( - func=subtract_numbers, name=f"{self.prefix}subtract_numbers" + func=subtract_numbers, ) print(f"SimpleMathToolset initialized with prefix '{self.prefix}'") async def get_tools( self, readonly_context: Optional[ReadonlyContext] = None ) -> List[BaseTool]: - print(f"SimpleMathToolset.get_tools() called.") + print("SimpleMathToolset.get_tools() called.") # Example of dynamic behavior: # Could use readonly_context.state to decide which tools to return # For instance, if readonly_context.state.get("enable_advanced_math"): @@ -97,12 +98,12 @@ def greet_user(name: str = "User") -> Dict[str, str]: greet_tool = FunctionTool(func=greet_user) # 4. Instantiate the toolset -math_toolset_instance = SimpleMathToolset(prefix="calculator_") +math_toolset_instance = SimpleMathToolset(prefix="calculator") # 5. Define an agent that uses both the individual tool and the toolset calculator_agent = LlmAgent( name="CalculatorAgent", - model="gemini-2.0-flash", # Replace with your desired model + model="gemini-flash-latest", # Replace with your desired model instruction="You are a helpful calculator and greeter. " "Use 'greet_user' for greetings. " "Use 'calculator_add_numbers' to add and 'calculator_subtract_numbers' to subtract. " @@ -110,31 +111,17 @@ def greet_user(name: str = "User") -> Dict[str, str]: tools=[greet_tool, math_toolset_instance], # Individual tool # Toolset instance ) -## --8<-- [end:init] +# 6. Run the agent +runner = InMemoryRunner(agent=calculator_agent, app_name="toolset_example_app") -# print(f"Agent '{calculator_agent.name}' created.") +async def main(): + print("\n--- Query 1: Greeting ---") + await runner.run_debug("Hi there!") + print("\n--- Query 2: Addition ---") + await runner.run_debug("What is 5 plus 3?") + await math_toolset_instance.close() -# async def main(): -# session_service = InMemorySessionService() -# runner = Runner( -# agent=calculator_agent, -# app_name="toolset_example_app", -# session_service=session_service -# ) -# session = await session_service.create_session(app_name="toolset_example_app", user_id="test_user") -# -# user_query1 = Content(parts=[Part(text="Hi there!")]) -# print("\n--- Query 1: Greeting ---") -# async for event in runner.run_async(session_id=session.id, new_message=user_query1): -# if event.is_final_response(): print(f"Agent Response: {event.content.parts[0].text}") -# -# user_query2 = Content(parts=[Part(text="What is 5 plus 3?")]) -# print("\n--- Query 2: Addition ---") -# async for event in runner.run_async(session_id=session.id, new_message=user_query2): -# if event.is_final_response(): print(f"Agent Response: {event.content.parts[0].text}") -# -# # Important: Clean up the toolset if it manages resources -# await math_toolset_instance.close() -# -# if __name__ == "__main__": -# asyncio.run(main()) +if __name__ == "__main__": + asyncio.run(main()) + +## --8<-- [end:init] diff --git a/mkdocs.yml b/mkdocs.yml index 99e0d446c6..feeadc65ab 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -345,6 +345,8 @@ nav: - Observability: - observability/index.md - Logging: observability/logging.md + - Metrics: observability/metrics.md + - Traces: observability/traces.md - Evaluation: - evaluate/index.md - Criteria: evaluate/criteria.md