feat(tool/looker): add support for "description" field in looker tool (#1199)

## Description
---
Adds support for fetching description for looker dimensions and
measures.
Descriptions give important context to any agent about how to filter and
aggregate.

Adds the `description` DimensionFields and MeasureFields
Handles cases when description is not available.
Adds tests.

## PR Checklist
---
- [X] Make sure you reviewed

[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [X] Make sure to open an issue as a

[bug/issue](https://github.com/googleapis/langchain-google-alloydb-pg-python/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
  designs, and agree on the general idea
- [X] Ensure the tests and linter pass
- [X] Code coverage does not decrease (if any source code was changed)
- [X] Appropriate docs were updated (if necessary)
- [X] Make sure to add `!` if this involve a breaking change

🛠️ Fixes #1197

---------

Co-authored-by: Magnus Benediktsson <magnus.benediktsson@nordnet.se>
This commit is contained in:
Dr. Strangelove
2025-08-20 15:41:18 -04:00
committed by GitHub
parent 5dcc66c84f
commit 97f0dd2acf
3 changed files with 179 additions and 9 deletions

View File

@@ -24,15 +24,20 @@ import (
)
const (
DimensionsFields = "fields(dimensions(name,type,label,label_short))"
FiltersFields = "fields(filters(name,type,label,label_short))"
MeasuresFields = "fields(measures(name,type,label,label_short))"
ParametersFields = "fields(parameters(name,type,label,label_short))"
DimensionsFields = "fields(dimensions(name,type,label,label_short,description))"
FiltersFields = "fields(filters(name,type,label,label_short,description))"
MeasuresFields = "fields(measures(name,type,label,label_short,description))"
ParametersFields = "fields(parameters(name,type,label,label_short,description))"
)
// ExtractLookerFieldProperties extracts common properties from Looker field objects.
func ExtractLookerFieldProperties(ctx context.Context, fields *[]v4.LookmlModelExploreField) ([]any, error) {
var data []any
data := make([]any, 0)
// Handle nil fields pointer
if fields == nil {
return data, nil
}
logger, err := util.LoggerFromContext(ctx)
if err != nil {
@@ -56,6 +61,9 @@ func ExtractLookerFieldProperties(ctx context.Context, fields *[]v4.LookmlModelE
if v.LabelShort != nil {
vMap["label_short"] = *v.LabelShort
}
if v.Description != nil {
vMap["description"] = *v.Description
}
logger.DebugContext(ctx, "Converted to %v\n", vMap)
data = append(data, vMap)
}

View File

@@ -0,0 +1,162 @@
// Copyright 2025 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 lookercommon_test
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/testutils"
"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
)
func TestExtractLookerFieldProperties(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
// Helper function to create string pointers
stringPtr := func(s string) *string { return &s }
tcs := []struct {
desc string
fields []v4.LookmlModelExploreField
want []any
}{
{
desc: "field with all properties including description",
fields: []v4.LookmlModelExploreField{
{
Name: stringPtr("dimension_name"),
Type: stringPtr("string"),
Label: stringPtr("Dimension Label"),
LabelShort: stringPtr("Dim Label"),
Description: stringPtr("This is a dimension description"),
},
},
want: []any{
map[string]any{
"name": "dimension_name",
"type": "string",
"label": "Dimension Label",
"label_short": "Dim Label",
"description": "This is a dimension description",
},
},
},
{
desc: "field with missing description",
fields: []v4.LookmlModelExploreField{
{
Name: stringPtr("dimension_name"),
Type: stringPtr("string"),
Label: stringPtr("Dimension Label"),
LabelShort: stringPtr("Dim Label"),
// Description is nil
},
},
want: []any{
map[string]any{
"name": "dimension_name",
"type": "string",
"label": "Dimension Label",
"label_short": "Dim Label",
// description should not be present in the map
},
},
},
{
desc: "field with only required fields",
fields: []v4.LookmlModelExploreField{
{
Name: stringPtr("simple_dimension"),
Type: stringPtr("number"),
},
},
want: []any{
map[string]any{
"name": "simple_dimension",
"type": "number",
},
},
},
{
desc: "empty fields list",
fields: []v4.LookmlModelExploreField{},
want: []any{},
},
{
desc: "multiple fields with mixed properties",
fields: []v4.LookmlModelExploreField{
{
Name: stringPtr("dim1"),
Type: stringPtr("string"),
Label: stringPtr("First Dimension"),
Description: stringPtr("First dimension description"),
},
{
Name: stringPtr("dim2"),
Type: stringPtr("number"),
LabelShort: stringPtr("Dim2"),
},
},
want: []any{
map[string]any{
"name": "dim1",
"type": "string",
"label": "First Dimension",
"description": "First dimension description",
},
map[string]any{
"name": "dim2",
"type": "number",
"label_short": "Dim2",
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got, err := lookercommon.ExtractLookerFieldProperties(ctx, &tc.fields)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("incorrect result: diff %v", diff)
}
})
}
}
func TestExtractLookerFieldPropertiesWithNilFields(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
got, err := lookercommon.ExtractLookerFieldProperties(ctx, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := []any{}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("incorrect result: diff %v", diff)
}
}

View File

@@ -624,16 +624,16 @@ func TestLooker(t *testing.T) {
wantResult = "{\"group_label\":\"System Activity\",\"label\":\"Content Usage\",\"name\":\"content_usage\"}"
tests.RunToolInvokeParametersTest(t, "get_explores", []byte(`{"model": "system__activity"}`), wantResult)
wantResult = "{\"label\":\"Content Usage API Count\",\"label_short\":\"API Count\",\"name\":\"content_usage.api_count\",\"type\":\"number\"}"
wantResult = "{\"description\":\"Number of times this content has been viewed via the Looker API\",\"label\":\"Content Usage API Count\",\"label_short\":\"API Count\",\"name\":\"content_usage.api_count\",\"type\":\"number\"}"
tests.RunToolInvokeParametersTest(t, "get_dimensions", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
wantResult = "{\"label\":\"Content Usage API Total\",\"label_short\":\"API Total\",\"name\":\"content_usage.api_total\",\"type\":\"sum\"}"
wantResult = "{\"description\":\"The total number of views via the Looker API\",\"label\":\"Content Usage API Total\",\"label_short\":\"API Total\",\"name\":\"content_usage.api_total\",\"type\":\"sum\"}"
tests.RunToolInvokeParametersTest(t, "get_measures", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
wantResult = "null"
wantResult = "[]"
tests.RunToolInvokeParametersTest(t, "get_filters", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
wantResult = "null"
wantResult = "[]"
tests.RunToolInvokeParametersTest(t, "get_parameters", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
wantResult = "{\"look.count\":"