mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-11 16:38:15 -05:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
162
internal/tools/looker/lookercommon/lookercommon_test.go
Normal file
162
internal/tools/looker/lookercommon/lookercommon_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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\":"
|
||||
|
||||
Reference in New Issue
Block a user