Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 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
1 change: 1 addition & 0 deletions cmd/internal/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/http"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardelement"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardfilter"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeragent"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerconversationalanalytics"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercreateprojectdirectory"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercreateprojectfile"
Expand Down
72 changes: 72 additions & 0 deletions docs/en/resources/tools/looker-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: Looker Agent
description: Manage Looker Agents
keywords:
- looker
- looker-agent
- agent
---

# Looker Agent

The `looker-agent` tool allows LLMs to manage Looker Agents. It supports listing, retrieving, creating, and deleting agents using the Looker Go SDK.

## Configuration

To use the `looker-agent` tool, you must define it in your `server.yaml` file.

```yaml
tools:
- name: looker_agent_manage
type: looker-agent
source: my_looker_source
description: Manage Looker AI Agents.
```

## Parameters

| Parameter | Type | Required | Description |
| :---------- | :------- | :------- | :---------- |
| `operation` | `string` | Yes | The operation to perform. Must be one of: `list`, `get`, `create`, or `delete`. |
| `agent_id` | `string` | No | The ID of the agent. Required for `get` and `delete` operations. |
| `name` | `string` | No | The name of the agent. Required for `create` operation. |

## Operations

### List Agents
Retrieve a list of all agents.
```json
{
"operation": "list"
}
```

### Get Agent
Retrieve details of a specific agent by its ID.
```json
{
"operation": "get",
"agent_id": "12345"
}
```

### Create Agent
Create a new agent with the given name.
```json
{
"operation": "create",
"name": "My AI Assistant"
}
```

### Delete Agent
Delete an agent by its ID.
```json
{
"operation": "delete",
"agent_id": "12345"
}
```

## Dependencies
This tool requires the underlying Looker Go SDK to support the `Agent` API resource (v4.0.26+).
77 changes: 77 additions & 0 deletions docs/en/resources/tools/looker/looker-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: "looker-agent"
type: docs
weight: 1
description: >
Manage Looker Agents
aliases:
- /resources/tools/looker-agent
---

# Looker Agent

The `looker-agent` tool allows LLMs to manage Looker Agents. It supports listing, retrieving, creating, and deleting agents using the Looker Go SDK.

It's compatible with the following sources:

- [looker](../../sources/looker.md)

## Configuration

To use the `looker-agent` tool, you must define it in your `server.yaml` file.

```yaml
tools:
- name: looker_agent_manage
type: looker-agent
source: my_looker_source
description: Manage Looker AI Agents.
```

## Parameters

| **Parameter** | **Type** | **Required** | **Description** |
|:-------------|:--------:|:------------:|:----------------|
| `operation` | `string` | Yes | The operation to perform. Must be one of: `list`, `get`, `create`, or `delete`. |
| `agent_id` | `string` | No | The ID of the agent. Required for `get` and `delete` operations. |
| `name` | `string` | No | The name of the agent. Required for `create` operation. |

## Operations

### List Agents
Retrieve a list of all agents.
```json
{
"operation": "list"
}
```

### Get Agent
Retrieve details of a specific agent by its ID.
```json
{
"operation": "get",
"agent_id": "12345"
}
```

### Create Agent
Create a new agent with the given name.
```json
{
"operation": "create",
"name": "My AI Assistant"
}
```

### Delete Agent
Delete an agent by its ID.
```json
{
"operation": "delete",
"agent_id": "12345"
}
```

## Dependencies
This tool requires the underlying Looker Go SDK to support the `Agent` API resource (v0.26.6+).
205 changes: 205 additions & 0 deletions internal/tools/looker/lookeragent/lookeragent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lookeragent

import (
"context"
"fmt"
"net/http"

yaml "github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/util"
"github.com/googleapis/genai-toolbox/internal/util/parameters"

"github.com/looker-open-source/sdk-codegen/go/rtl"
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
)

const resourceType string = "looker-agent"

func init() {
if !tools.Register(resourceType, newConfig) {
panic(fmt.Sprintf("tool type %q already registered", resourceType))
}
}

func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}

type compatibleSource interface {
UseClientAuthorization() bool
GetAuthTokenHeaderName() string
LookerApiSettings() *rtl.ApiSettings
GetLookerSDK(string) (*v4.LookerSDK, error)
}

type Config struct {
Name string `yaml:"name" validate:"required"`
Type string `yaml:"type" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Annotations *tools.ToolAnnotations `yaml:"annotations,omitempty"`
}

// validate interface
var _ tools.ToolConfig = Config{}

func (cfg Config) ToolConfigType() string {
return resourceType
}

func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
operationParameter := parameters.NewStringParameter("operation", "The operation to perform. Must be one of: `list`, `get`, `create`, or `delete`.")
agentIdParameter := parameters.NewStringParameterWithDefault("agent_id", "", "The ID of the agent. Required for `get` and `delete` operations.")
nameParameter := parameters.NewStringParameterWithDefault("name", "", "The name of the agent. Required for `create` operation.")
params := parameters.Parameters{operationParameter, agentIdParameter, nameParameter}

annotations := cfg.Annotations

mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, annotations)

return Tool{
Config: cfg,
Parameters: params,
manifest: tools.Manifest{
Description: cfg.Description,
Parameters: params.Manifest(),
AuthRequired: cfg.AuthRequired,
},
mcpManifest: mcpManifest,
}, nil
}

// validate interface
var _ tools.Tool = Tool{}

type Tool struct {
Config
Parameters parameters.Parameters `yaml:"parameters"`
manifest tools.Manifest
mcpManifest tools.McpManifest
}

func (t Tool) ToConfig() tools.ToolConfig {
return t.Config
}

func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, util.ToolboxError) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return nil, util.NewClientServerError("source used is not compatible with the tool", http.StatusInternalServerError, err)
}

logger, err := util.LoggerFromContext(ctx)
if err != nil {
return nil, util.NewClientServerError("unable to get logger from ctx", http.StatusInternalServerError, err)
}

sdk, err := source.GetLookerSDK(string(accessToken))
if err != nil {
return nil, util.NewClientServerError(fmt.Sprintf("error getting sdk: %v", err), http.StatusInternalServerError, err)
}

mapParams := params.AsMap()
logger.DebugContext(ctx, "looker_agent params = ", mapParams)
operation := mapParams["operation"].(string)
agentId := mapParams["agent_id"].(string)
name := mapParams["name"].(string)

switch operation {
case "list":
resp, err := sdk.SearchAgents(v4.RequestSearchAgents{}, source.LookerApiSettings())
if err != nil {
return nil, util.NewClientServerError(fmt.Sprintf("error making search_agents request: %s", err), http.StatusInternalServerError, err)
}
return resp, nil
case "get":
if agentId == "" {
return nil, util.NewClientServerError(fmt.Sprintf("%s operation: agent_id must be specified", operation), http.StatusInternalServerError, nil)
}
resp, err := sdk.GetAgent(agentId, "", source.LookerApiSettings())
if err != nil {
return nil, util.NewClientServerError(fmt.Sprintf("error making get_agent request: %s", err), http.StatusInternalServerError, err)
}
return resp, nil
case "create":
if name == "" {
return nil, util.NewClientServerError(fmt.Sprintf("%s operation: name must be specified", operation), http.StatusInternalServerError, nil)
}
body := v4.WriteAgent{
Name: &name,
}
resp, err := sdk.CreateAgent(body, "", source.LookerApiSettings())
if err != nil {
return nil, util.NewClientServerError(fmt.Sprintf("error making create_agent request: %s", err), http.StatusInternalServerError, err)
}
return resp, nil
case "delete":
if agentId == "" {
return nil, util.NewClientServerError(fmt.Sprintf("%s operation: agent_id must be specified", operation), http.StatusInternalServerError, nil)
}
resp, err := sdk.DeleteAgent(agentId, "", source.LookerApiSettings())
if err != nil {
return nil, util.NewClientServerError(fmt.Sprintf("error making delete_agent request: %s", err), http.StatusInternalServerError, err)
}
return resp, nil
default:
return nil, util.NewClientServerError(fmt.Sprintf("unknown operation: %s. Must be one of `list`, `get`, `create`, or `delete`", operation), http.StatusInternalServerError, nil)
}
}

func (t Tool) EmbedParams(ctx context.Context, paramValues parameters.ParamValues, embeddingModelsMap map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
return parameters.EmbedParams(ctx, t.Parameters, paramValues, embeddingModelsMap, nil)
}

func (t Tool) Manifest() tools.Manifest {
return t.manifest
}

func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}

func (t Tool) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return false, err
}
return source.UseClientAuthorization(), nil
}

func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Type)
if err != nil {
return "", err
}
return source.GetAuthTokenHeaderName(), nil
}

func (t Tool) GetParameters() parameters.Parameters {
return t.Parameters
}
Loading