Skip to content

Commit 97f0dd2

Browse files
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>
1 parent 5dcc66c commit 97f0dd2

3 files changed

Lines changed: 179 additions & 9 deletions

File tree

‎internal/tools/looker/lookercommon/lookercommon.go‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,20 @@ import (
2424
)
2525

2626
const (
27-
DimensionsFields = "fields(dimensions(name,type,label,label_short))"
28-
FiltersFields = "fields(filters(name,type,label,label_short))"
29-
MeasuresFields = "fields(measures(name,type,label,label_short))"
30-
ParametersFields = "fields(parameters(name,type,label,label_short))"
27+
DimensionsFields = "fields(dimensions(name,type,label,label_short,description))"
28+
FiltersFields = "fields(filters(name,type,label,label_short,description))"
29+
MeasuresFields = "fields(measures(name,type,label,label_short,description))"
30+
ParametersFields = "fields(parameters(name,type,label,label_short,description))"
3131
)
3232

3333
// ExtractLookerFieldProperties extracts common properties from Looker field objects.
3434
func ExtractLookerFieldProperties(ctx context.Context, fields *[]v4.LookmlModelExploreField) ([]any, error) {
35-
var data []any
35+
data := make([]any, 0)
36+
37+
// Handle nil fields pointer
38+
if fields == nil {
39+
return data, nil
40+
}
3641

3742
logger, err := util.LoggerFromContext(ctx)
3843
if err != nil {
@@ -56,6 +61,9 @@ func ExtractLookerFieldProperties(ctx context.Context, fields *[]v4.LookmlModelE
5661
if v.LabelShort != nil {
5762
vMap["label_short"] = *v.LabelShort
5863
}
64+
if v.Description != nil {
65+
vMap["description"] = *v.Description
66+
}
5967
logger.DebugContext(ctx, "Converted to %v\n", vMap)
6068
data = append(data, vMap)
6169
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package lookercommon_test
16+
17+
import (
18+
"testing"
19+
20+
"github.com/google/go-cmp/cmp"
21+
"github.com/googleapis/genai-toolbox/internal/testutils"
22+
"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
23+
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
24+
)
25+
26+
func TestExtractLookerFieldProperties(t *testing.T) {
27+
ctx, err := testutils.ContextWithNewLogger()
28+
if err != nil {
29+
t.Fatalf("unexpected error: %s", err)
30+
}
31+
32+
// Helper function to create string pointers
33+
stringPtr := func(s string) *string { return &s }
34+
35+
tcs := []struct {
36+
desc string
37+
fields []v4.LookmlModelExploreField
38+
want []any
39+
}{
40+
{
41+
desc: "field with all properties including description",
42+
fields: []v4.LookmlModelExploreField{
43+
{
44+
Name: stringPtr("dimension_name"),
45+
Type: stringPtr("string"),
46+
Label: stringPtr("Dimension Label"),
47+
LabelShort: stringPtr("Dim Label"),
48+
Description: stringPtr("This is a dimension description"),
49+
},
50+
},
51+
want: []any{
52+
map[string]any{
53+
"name": "dimension_name",
54+
"type": "string",
55+
"label": "Dimension Label",
56+
"label_short": "Dim Label",
57+
"description": "This is a dimension description",
58+
},
59+
},
60+
},
61+
{
62+
desc: "field with missing description",
63+
fields: []v4.LookmlModelExploreField{
64+
{
65+
Name: stringPtr("dimension_name"),
66+
Type: stringPtr("string"),
67+
Label: stringPtr("Dimension Label"),
68+
LabelShort: stringPtr("Dim Label"),
69+
// Description is nil
70+
},
71+
},
72+
want: []any{
73+
map[string]any{
74+
"name": "dimension_name",
75+
"type": "string",
76+
"label": "Dimension Label",
77+
"label_short": "Dim Label",
78+
// description should not be present in the map
79+
},
80+
},
81+
},
82+
{
83+
desc: "field with only required fields",
84+
fields: []v4.LookmlModelExploreField{
85+
{
86+
Name: stringPtr("simple_dimension"),
87+
Type: stringPtr("number"),
88+
},
89+
},
90+
want: []any{
91+
map[string]any{
92+
"name": "simple_dimension",
93+
"type": "number",
94+
},
95+
},
96+
},
97+
{
98+
desc: "empty fields list",
99+
fields: []v4.LookmlModelExploreField{},
100+
want: []any{},
101+
},
102+
{
103+
desc: "multiple fields with mixed properties",
104+
fields: []v4.LookmlModelExploreField{
105+
{
106+
Name: stringPtr("dim1"),
107+
Type: stringPtr("string"),
108+
Label: stringPtr("First Dimension"),
109+
Description: stringPtr("First dimension description"),
110+
},
111+
{
112+
Name: stringPtr("dim2"),
113+
Type: stringPtr("number"),
114+
LabelShort: stringPtr("Dim2"),
115+
},
116+
},
117+
want: []any{
118+
map[string]any{
119+
"name": "dim1",
120+
"type": "string",
121+
"label": "First Dimension",
122+
"description": "First dimension description",
123+
},
124+
map[string]any{
125+
"name": "dim2",
126+
"type": "number",
127+
"label_short": "Dim2",
128+
},
129+
},
130+
},
131+
}
132+
133+
for _, tc := range tcs {
134+
t.Run(tc.desc, func(t *testing.T) {
135+
got, err := lookercommon.ExtractLookerFieldProperties(ctx, &tc.fields)
136+
if err != nil {
137+
t.Fatalf("unexpected error: %v", err)
138+
}
139+
140+
if diff := cmp.Diff(tc.want, got); diff != "" {
141+
t.Fatalf("incorrect result: diff %v", diff)
142+
}
143+
})
144+
}
145+
}
146+
147+
func TestExtractLookerFieldPropertiesWithNilFields(t *testing.T) {
148+
ctx, err := testutils.ContextWithNewLogger()
149+
if err != nil {
150+
t.Fatalf("unexpected error: %s", err)
151+
}
152+
153+
got, err := lookercommon.ExtractLookerFieldProperties(ctx, nil)
154+
if err != nil {
155+
t.Fatalf("unexpected error: %v", err)
156+
}
157+
158+
want := []any{}
159+
if diff := cmp.Diff(want, got); diff != "" {
160+
t.Fatalf("incorrect result: diff %v", diff)
161+
}
162+
}

‎tests/looker/looker_integration_test.go‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -624,16 +624,16 @@ func TestLooker(t *testing.T) {
624624
wantResult = "{\"group_label\":\"System Activity\",\"label\":\"Content Usage\",\"name\":\"content_usage\"}"
625625
tests.RunToolInvokeParametersTest(t, "get_explores", []byte(`{"model": "system__activity"}`), wantResult)
626626

627-
wantResult = "{\"label\":\"Content Usage API Count\",\"label_short\":\"API Count\",\"name\":\"content_usage.api_count\",\"type\":\"number\"}"
627+
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\"}"
628628
tests.RunToolInvokeParametersTest(t, "get_dimensions", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
629629

630-
wantResult = "{\"label\":\"Content Usage API Total\",\"label_short\":\"API Total\",\"name\":\"content_usage.api_total\",\"type\":\"sum\"}"
630+
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\"}"
631631
tests.RunToolInvokeParametersTest(t, "get_measures", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
632632

633-
wantResult = "null"
633+
wantResult = "[]"
634634
tests.RunToolInvokeParametersTest(t, "get_filters", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
635635

636-
wantResult = "null"
636+
wantResult = "[]"
637637
tests.RunToolInvokeParametersTest(t, "get_parameters", []byte(`{"model": "system__activity", "explore": "content_usage"}`), wantResult)
638638

639639
wantResult = "{\"look.count\":"

0 commit comments

Comments
 (0)