mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-09 15:38:08 -05:00
feat(healthcare): add support for healthcare source, tool and prebuilt config (#1853)
## Description Add support for healthcare source, tool and prebuilt config. This branch consist of all previously approved PRs. 🛠️ Fixes #1648 --------- Co-authored-by: Marwan Tammam <15021613+Quarz0@users.noreply.github.com>
This commit is contained in:
@@ -214,6 +214,29 @@ steps:
|
||||
dataform \
|
||||
dataform
|
||||
|
||||
- id: "cloud-healthcare"
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
entrypoint: /bin/bash
|
||||
env:
|
||||
- "GOPATH=/gopath"
|
||||
- "HEALTHCARE_PROJECT=$PROJECT_ID"
|
||||
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
|
||||
- "HEALTHCARE_REGION=$_REGION"
|
||||
- "HEALTHCARE_DATASET=$_HEALTHCARE_DATASET"
|
||||
- "HEALTHCARE_PREPOPULATED_DICOM_STORE=$_HEALTHCARE_PREPOPULATED_DICOM_STORE"
|
||||
secretEnv: ["CLIENT_ID"]
|
||||
volumes:
|
||||
- name: "go"
|
||||
path: "/gopath"
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
.ci/test_with_coverage.sh \
|
||||
"Cloud Healthcare API" \
|
||||
cloudhealthcare \
|
||||
cloudhealthcare
|
||||
|
||||
- id: "postgres"
|
||||
name: golang:1
|
||||
waitFor: ["compile-test-binary"]
|
||||
@@ -937,6 +960,8 @@ substitutions:
|
||||
_ALLOYDB_AI_NL_CLUSTER: "alloydb-ai-nl-testing"
|
||||
_ALLOYDB_AI_NL_INSTANCE: "alloydb-ai-nl-testing-instance"
|
||||
_BIGTABLE_INSTANCE: "bigtable-testing-instance"
|
||||
_HEALTHCARE_DATASET: "test-dataset"
|
||||
_HEALTHCARE_PREPOPULATED_DICOM_STORE: "prepopulated-test-dicom-store"
|
||||
_POSTGRES_HOST: 127.0.0.1
|
||||
_POSTGRES_PORT: "5432"
|
||||
_SPANNER_INSTANCE: "spanner-testing"
|
||||
|
||||
16
cmd/root.go
16
cmd/root.go
@@ -69,6 +69,21 @@ import (
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhouselistdatabases"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhouselisttables"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhousesql"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirfetchpage"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirpatienteverything"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirpatientsearch"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdataset"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstore"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstoremetrics"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirresource"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstore"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstoremetrics"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarelistdicomstores"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarelistfhirstores"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcareretrieverendereddicominstance"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicominstances"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomseries"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomstudies"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudmonitoring"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudsql/cloudsqlcreatedatabase"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudsql/cloudsqlcreateusers"
|
||||
@@ -192,6 +207,7 @@ import (
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/bigtable"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/cassandra"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/clickhouse"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudmonitoring"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin"
|
||||
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmssql"
|
||||
|
||||
@@ -1256,6 +1256,7 @@ func TestPrebuiltTools(t *testing.T) {
|
||||
cloudsqlmysqlobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-mysql-observability")
|
||||
cloudsqlmssqlobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-mssql-observability")
|
||||
serverless_spark_config, _ := prebuiltconfigs.Get("serverless-spark")
|
||||
cloudhealthcare_config, _ := prebuiltconfigs.Get("cloud-healthcare")
|
||||
|
||||
// Set environment variables
|
||||
t.Setenv("API_KEY", "your_api_key")
|
||||
@@ -1349,6 +1350,10 @@ func TestPrebuiltTools(t *testing.T) {
|
||||
t.Setenv("NEO4J_USERNAME", "your_neo4j_user")
|
||||
t.Setenv("NEO4J_PASSWORD", "your_neo4j_password")
|
||||
|
||||
t.Setenv("CLOUD_HEALTHCARE_PROJECT", "your_gcp_project_id")
|
||||
t.Setenv("CLOUD_HEALTHCARE_REGION", "your_gcp_region")
|
||||
t.Setenv("CLOUD_HEALTHCARE_DATASET", "your_healthcare_dataset")
|
||||
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
@@ -1628,6 +1633,24 @@ func TestPrebuiltTools(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cloud healthcare prebuilt tools",
|
||||
in: cloudhealthcare_config,
|
||||
wantToolset: server.ToolsetConfigs{
|
||||
"cloud_healthcare_dataset_tools": tools.ToolsetConfig{
|
||||
Name: "cloud_healthcare_dataset_tools",
|
||||
ToolNames: []string{"get_dataset", "list_dicom_stores", "list_fhir_stores"},
|
||||
},
|
||||
"cloud_healthcare_fhir_tools": tools.ToolsetConfig{
|
||||
Name: "cloud_healthcare_fhir_tools",
|
||||
ToolNames: []string{"get_fhir_store", "get_fhir_store_metrics", "get_fhir_resource", "fhir_patient_search", "fhir_patient_everything", "fhir_fetch_page"},
|
||||
},
|
||||
"cloud_healthcare_dicom_tools": tools.ToolsetConfig{
|
||||
Name: "cloud_healthcare_dicom_tools",
|
||||
ToolNames: []string{"get_dicom_store", "get_dicom_store_metrics", "search_dicom_studies", "search_dicom_series", "search_dicom_instances", "retrieve_rendered_dicom_instance"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
|
||||
@@ -590,3 +590,33 @@ details on how to connect your AI tools (IDEs) to databases via Toolbox and MCP.
|
||||
* **Tools:**
|
||||
* `execute_cypher`: Executes a Cypher query.
|
||||
* `get_schema`: Retrieves the schema of the Neo4j database.
|
||||
|
||||
## Google Cloud Healthcare API
|
||||
* `--prebuilt` value: `cloud-healthcare`
|
||||
* **Environment Variables:**
|
||||
* `CLOUD_HEALTHCARE_PROJECT`: The GCP project ID.
|
||||
* `CLOUD_HEALTHCARE_REGION`: The Cloud Healthcare API dataset region.
|
||||
* `CLOUD_HEALTHCARE_DATASET`: The Cloud Healthcare API dataset ID.
|
||||
* `CLOUD_HEALTHCARE_USE_CLIENT_OAUTH`: (Optional) If `true`, forwards the client's
|
||||
OAuth access token for authentication. Defaults to `false`.
|
||||
* **Permissions:**
|
||||
* **Healthcare FHIR Resource Reader** (`roles/healthcare.fhirResourceReader`) to read an
|
||||
search FHIR resources.
|
||||
* **Healthcare DICOM Viewer** (`roles/healthcare.dicomViewer`) to retrieve DICOM images from a
|
||||
DICOM store.
|
||||
* **Tools:**
|
||||
* `get_dataset`: Gets information about a Cloud Healthcare API dataset.
|
||||
* `list_dicom_stores`: Lists DICOM stores in a Cloud Healthcare API dataset.
|
||||
* `list_fhir_stores`: Lists FHIR stores in a Cloud Healthcare API dataset.
|
||||
* `get_fhir_store`: Gets information about a FHIR store.
|
||||
* `get_fhir_store_metrics`: Gets metrics for a FHIR store.
|
||||
* `get_fhir_resource`: Gets a FHIR resource from a FHIR store.
|
||||
* `fhir_patient_search`: Searches for patient resource(s) based on a set of criteria.
|
||||
* `fhir_patient_everything`: Retrieves resources related to a given patient.
|
||||
* `fhir_fetch_page`: Fetches a page of FHIR resources.
|
||||
* `get_dicom_store`: Gets information about a DICOM store.
|
||||
* `get_dicom_store_metrics`: Gets metrics for a DICOM store.
|
||||
* `search_dicom_studies`: Searches for DICOM studies.
|
||||
* `search_dicom_series`: Searches for DICOM series.
|
||||
* `search_dicom_instances`: Searches for DICOM instances.
|
||||
* `retrieve_rendered_dicom_instance`: Retrieves a rendered DICOM instance.
|
||||
|
||||
164
docs/en/resources/sources/cloud-healthcare.md
Normal file
164
docs/en/resources/sources/cloud-healthcare.md
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: "Cloud Healthcare API"
|
||||
linkTitle: "Cloud Healthcare"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
The Cloud Healthcare API provides a managed solution for storing and
|
||||
accessing healthcare data in Google Cloud, providing a critical bridge
|
||||
between existing care systems and applications hosted on Google Cloud.
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
The [Cloud Healthcare API][healthcare-docs] provides a managed solution
|
||||
for storing and accessing healthcare data in Google Cloud, providing a
|
||||
critical bridge between existing care systems and applications hosted on
|
||||
Google Cloud. It supports healthcare data standards such as HL7® FHIR®,
|
||||
HL7® v2, and DICOM®. It provides a fully managed, highly scalable,
|
||||
enterprise-grade development environment for building clinical and analytics
|
||||
solutions securely on Google Cloud.
|
||||
|
||||
A dataset is a container in your Google Cloud project that holds modality-specific
|
||||
healthcare data. Datasets contain other data stores, such as FHIR stores and DICOM
|
||||
stores, which in turn hold their own types of healthcare data.
|
||||
|
||||
A single dataset can contain one or many data stores, and those stores can all service
|
||||
the same modality or different modalities as application needs dictate. Using multiple
|
||||
stores in the same dataset might be appropriate in various situations.
|
||||
|
||||
If you are new to the Cloud Healthcare API, you can try to
|
||||
[create and view datasets and stores using curl][healthcare-quickstart-curl].
|
||||
|
||||
[healthcare-docs]: https://cloud.google.com/healthcare/docs
|
||||
[healthcare-quickstart-curl]:
|
||||
https://cloud.google.com/healthcare-api/docs/store-healthcare-data-rest
|
||||
|
||||
## Available Tools
|
||||
|
||||
- [`cloud-healthcare-get-dataset`](../tools/cloudhealthcare/cloud-healthcare-get-dataset.md)
|
||||
Retrieves a dataset’s details.
|
||||
|
||||
- [`cloud-healthcare-list-fhir-stores`](../tools/cloudhealthcare/cloud-healthcare-list-fhir-stores.md)
|
||||
Lists the available FHIR stores in the healthcare dataset.
|
||||
|
||||
- [`cloud-healthcare-list-dicom-stores`](../tools/cloudhealthcare/cloud-healthcare-list-dicom-stores.md)
|
||||
Lists the available DICOM stores in the healthcare dataset.
|
||||
|
||||
- [`cloud-healthcare-get-fhir-store`](../tools/cloudhealthcare/cloud-healthcare-get-fhir-store.md)
|
||||
Retrieves information about a FHIR store.
|
||||
|
||||
- [`cloud-healthcare-get-fhir-store-metrics`](../tools/cloudhealthcare/cloud-healthcare-get-fhir-store-metrics.md)
|
||||
Retrieves metrics for a FHIR store.
|
||||
|
||||
- [`cloud-healthcare-get-fhir-resource`](../tools/cloudhealthcare/cloud-healthcare-get-fhir-resource.md)
|
||||
Retrieves a specific FHIR resource from a FHIR store.
|
||||
|
||||
- [`cloud-healthcare-fhir-patient-search`](../tools/cloudhealthcare/cloud-healthcare-fhir-patient-search.md)
|
||||
Searches for patients in a FHIR store based on a set of criteria.
|
||||
|
||||
- [`cloud-healthcare-fhir-patient-everything`](../tools/cloudhealthcare/cloud-healthcare-fhir-patient-everything.md)
|
||||
Retrieves all information for a given patient.
|
||||
|
||||
- [`cloud-healthcare-fhir-fetch-page`](../tools/cloudhealthcare/cloud-healthcare-fhir-fetch-page.md)
|
||||
Fetches a page of FHIR resources from a given URL.
|
||||
|
||||
- [`cloud-healthcare-get-dicom-store`](../tools/cloudhealthcare/cloud-healthcare-get-dicom-store.md)
|
||||
Retrieves information about a DICOM store.
|
||||
|
||||
- [`cloud-healthcare-get-dicom-store-metrics`](../tools/cloudhealthcare/cloud-healthcare-get-dicom-store-metrics.md)
|
||||
Retrieves metrics for a DICOM store.
|
||||
|
||||
- [`cloud-healthcare-search-dicom-studies`](../tools/cloudhealthcare/cloud-healthcare-search-dicom-studies.md)
|
||||
Searches for DICOM studies in a DICOM store.
|
||||
|
||||
- [`cloud-healthcare-search-dicom-series`](../tools/cloudhealthcare/cloud-healthcare-search-dicom-series.md)
|
||||
Searches for DICOM series in a DICOM store.
|
||||
|
||||
- [`cloud-healthcare-search-dicom-instances`](../tools/cloudhealthcare/cloud-healthcare-search-dicom-instances.md)
|
||||
Searches for DICOM instances in a DICOM store.
|
||||
|
||||
- [`cloud-healthcare-retrieve-rendered-dicom-instance`](../tools/cloudhealthcare/cloud-healthcare-retrieve-rendered-dicom-instance.md)
|
||||
Retrieves a rendered DICOM instance from a DICOM store.
|
||||
|
||||
## Requirements
|
||||
|
||||
### IAM Permissions
|
||||
|
||||
The Cloud Healthcare API uses [Identity and Access Management (IAM)][iam-overview] to control
|
||||
user and group access to Cloud Healthcare resources like projects, datasets, and stores.
|
||||
|
||||
### Authentication via Application Default Credentials (ADC)
|
||||
|
||||
By **default**, Toolbox will use your [Application Default Credentials
|
||||
(ADC)][adc] to authorize and authenticate when interacting with the
|
||||
[Cloud Healthcare API][healthcare-docs].
|
||||
|
||||
When using this method, you need to ensure the IAM identity associated with your
|
||||
ADC (such as a service account) has the correct permissions for the queries you
|
||||
intend to run. Common roles include `roles/healthcare.fhirResourceReader` (which includes
|
||||
permissions to read and search for FHIR resources) or `roles/healthcare.dicomViewer` (for
|
||||
retrieving DICOM images).
|
||||
Follow this [guide][set-adc] to set up your ADC.
|
||||
|
||||
### Authentication via User's OAuth Access Token
|
||||
|
||||
If the `useClientOAuth` parameter is set to `true`, Toolbox will instead use the
|
||||
OAuth access token for authentication. This token is parsed from the
|
||||
`Authorization` header passed in with the tool invocation request. This method
|
||||
allows Toolbox to make queries to the [Cloud Healthcare API][healthcare-docs] on behalf of the
|
||||
client or the end-user.
|
||||
|
||||
When using this on-behalf-of authentication, you must ensure that the
|
||||
identity used has been granted the correct IAM permissions.
|
||||
|
||||
[iam-overview]: <https://cloud.google.com/healthcare/docs/access-control>
|
||||
[adc]: <https://cloud.google.com/docs/authentication#adc>
|
||||
[set-adc]: <https://cloud.google.com/docs/authentication/provide-credentials-adc>
|
||||
|
||||
## Example
|
||||
|
||||
Initialize a Cloud Healthcare API source that uses ADC:
|
||||
|
||||
```yaml
|
||||
sources:
|
||||
my-healthcare-source:
|
||||
kind: "cloud-healthcare"
|
||||
project: "my-project-id"
|
||||
region: "us-central1"
|
||||
dataset: "my-healthcare-dataset-id"
|
||||
# allowedFhirStores: # Optional: Restricts tool access to a specific list of FHIR store IDs.
|
||||
# - "my_fhir_store_1"
|
||||
# allowedDicomStores: # Optional: Restricts tool access to a specific list of DICOM store IDs.
|
||||
# - "my_dicom_store_1"
|
||||
# - "my_dicom_store_2"
|
||||
```
|
||||
|
||||
Initialize a Cloud Healthcare API source that uses the client's access token:
|
||||
|
||||
```yaml
|
||||
sources:
|
||||
my-healthcare-client-auth-source:
|
||||
kind: "cloud-healthcare"
|
||||
project: "my-project-id"
|
||||
region: "us-central1"
|
||||
dataset: "my-healthcare-dataset-id"
|
||||
useClientOAuth: true
|
||||
# allowedFhirStores: # Optional: Restricts tool access to a specific list of FHIR store IDs.
|
||||
# - "my_fhir_store_1"
|
||||
# allowedDicomStores: # Optional: Restricts tool access to a specific list of DICOM store IDs.
|
||||
# - "my_dicom_store_1"
|
||||
# - "my_dicom_store_2"
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|--------------------|:--------:|:------------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare". |
|
||||
| project | string | true | ID of the GCP project that the dataset lives in. |
|
||||
| region | string | true | Specifies the region (e.g., 'us', 'asia-northeast1') of the healthcare dataset. [Learn More](https://cloud.google.com/healthcare-api/docs/regions) |
|
||||
| dataset | string | true | ID of the healthcare dataset. |
|
||||
| allowedFhirStores | []string | false | An optional list of FHIR store IDs that tools using this source are allowed to access. If provided, any tool operation attempting to access a store not in this list will be rejected. If a single store is provided, it will be treated as the default for prebuilt tools. |
|
||||
| allowedDicomStores | []string | false | An optional list of DICOM store IDs that tools using this source are allowed to access. If provided, any tool operation attempting to access a store not in this list will be rejected. If a single store is provided, it will be treated as the default for prebuilt tools. |
|
||||
| useClientOAuth | bool | false | If true, forwards the client's OAuth access token from the "Authorization" header to downstream queries. |
|
||||
8
docs/en/resources/tools/cloudhealthcare/_index.md
Normal file
8
docs/en/resources/tools/cloudhealthcare/_index.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Cloud Healthcare API"
|
||||
linkTitle: "Cloud Healthcare"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
Tools that work with Cloud Healthcare Sources.
|
||||
---
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: "cloud-healthcare-fhir-fetch-page"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-fhir-fetch-page" tool fetches a page of FHIR resources from a given URL.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-fhir-fetch-page
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-fhir-fetch-page` tool fetches a page of FHIR resources from a given URL. It's
|
||||
compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-fhir-fetch-page` can be used for pagination when a previous tool call (like
|
||||
`cloud-healthcare-fhir-patient-search` or `cloud-healthcare-fhir-patient-everything`) returns a 'next' link in the response bundle.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
get_fhir_store:
|
||||
kind: cloud-healthcare-fhir-fetch-page
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to fetch a page of FHIR resources from a FHIR Bundle's entry.link.url
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-fhir-fetch-page". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-----------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| pageURL | string | true | The full URL of the FHIR page to fetch. This would usually be the value of `Bundle.entry.link.url` field within the response returned from FHIR search or FHIR patient everything operations. |
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: "cloud-healthcare-fhir-patient-everything"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-fhir-patient-everything" tool retrieves all information for a given patient.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-fhir-patient-everything
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-fhir-patient-everything` tool retrieves resources related to a given patient
|
||||
from a FHIR store. It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-fhir-patient-everything` returns all the information available for a given
|
||||
patient ID. It can be configured to only return certain resource types, or only
|
||||
resources that have been updated after a given time.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
fhir_patient_everything:
|
||||
kind: cloud-healthcare-fhir-patient-everything
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to retrieve all the information about a given patient.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|-----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-fhir-patient-everything". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|---------------------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| patientID | string | true | The ID of the patient FHIR resource for which the information is required. |
|
||||
| resourceTypesFilter | string | false | String of comma-delimited FHIR resource types. If provided, only resources of the specified resource type(s) are returned. |
|
||||
| sinceFilter | string | false | If provided, only resources updated after this time are returned. The time uses the format YYYY-MM-DDThh:mm:ss.sss+zz:zz. The time must be specified to the second and include a time zone. For example, 2015-02-07T13:28:17.239+02:00 or 2017-01-01T00:00:00Z. |
|
||||
| storeID | string | true* | The FHIR store ID to search in. |
|
||||
|
||||
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "cloud-healthcare-fhir-patient-search"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-fhir-patient-search" tool searches for patients in a FHIR store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-fhir-patient-search
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-fhir-patient-search` tool searches for patients in a FHIR store based on a
|
||||
set of criteria. It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-fhir-patient-search` returns a list of patients that match the given criteria.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
fhir_patient_search:
|
||||
kind: cloud-healthcare-fhir-patient-search
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to search for patients in the FHIR store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-fhir-patient-search". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|------------------|:--------:|:------------:|--------------------------------------------------------------------------------|
|
||||
| active | string | false | Whether the patient record is active. |
|
||||
| city | string | false | The city of the patient's address. |
|
||||
| country | string | false | The country of the patient's address. |
|
||||
| postalcode | string | false | The postal code of the patient's address. |
|
||||
| state | string | false | The state of the patient's address. |
|
||||
| addressSubstring | string | false | A substring to search for in any address field. |
|
||||
| birthDateRange | string | false | A date range for the patient's birth date in the format YYYY-MM-DD/YYYY-MM-DD. |
|
||||
| deathDateRange | string | false | A date range for the patient's death date in the format YYYY-MM-DD/YYYY-MM-DD. |
|
||||
| deceased | string | false | Whether the patient is deceased. |
|
||||
| email | string | false | The patient's email address. |
|
||||
| gender | string | false | The patient's gender. |
|
||||
| addressUse | string | false | The use of the patient's address. |
|
||||
| name | string | false | The patient's name. |
|
||||
| givenName | string | false | A portion of the given name of the patient. |
|
||||
| familyName | string | false | A portion of the family name of the patient. |
|
||||
| phone | string | false | The patient's phone number. |
|
||||
| language | string | false | The patient's preferred language. |
|
||||
| identifier | string | false | An identifier for the patient. |
|
||||
| summary | boolean | false | Requests the server to return a subset of the resource. True by default. |
|
||||
| storeID | string | true* | The FHIR store ID to search in. |
|
||||
|
||||
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "cloud-healthcare-get-dataset"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-get-dataset" tool retrieves metadata for the Healthcare dataset in the source.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-get-dataset
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-get-dataset` tool retrieves metadata for a Healthcare dataset.
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-get-dataset` returns the metadata of the healthcare dataset
|
||||
configured in the source. It takes no extra parameters.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
get_dataset:
|
||||
kind: cloud-healthcare-get-dataset
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to get healthcare dataset metadata.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:------------------------------------------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-get-dataset". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "cloud-healthcare-get-dicom-store-metrics"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-get-dicom-store-metrics" tool retrieves metrics for a DICOM store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-get-dicom-store-metrics
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-get-dicom-store-metrics` tool retrieves metrics for a DICOM store. It's
|
||||
compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-get-dicom-store-metrics` returns the metrics of a DICOM store.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
get_dicom_store_metrics:
|
||||
kind: cloud-healthcare-get-dicom-store-metrics
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to get metrics for a DICOM store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|-----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-get-dicom-store-metrics". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-----------|:--------:|:------------:|----------------------------------------|
|
||||
| storeID | string | true* | The DICOM store ID to get metrics for. |
|
||||
|
||||
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "cloud-healthcare-get-dicom-store"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-get-dicom-store" tool retrieves information about a DICOM store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-get-dicom-store
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-get-dicom-store` tool retrieves information about a DICOM store. It's
|
||||
compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-get-dicom-store` returns the details of a DICOM store.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
get_dicom_store:
|
||||
kind: cloud-healthcare-get-dicom-store
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to get information about a DICOM store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-get-dicom-store". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-----------|:--------:|:------------:|----------------------------------------|
|
||||
| storeID | string | true* | The DICOM store ID to get details for. |
|
||||
|
||||
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "cloud-healthcare-get-fhir-resource"
|
||||
linkTitle: "cloud-healthcare-get-fhir-resource"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-get-fhir-resource" tool retrieves a specific FHIR resource.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-get-fhir-resource
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-get-fhir-resource` tool retrieves a specific FHIR resource from a FHIR store.
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-get-fhir-resource` returns a single FHIR resource, identified by its type and ID.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
get_fhir_resource:
|
||||
kind: cloud-healthcare-get-fhir-resource
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to retrieve a specific FHIR resource.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-get-fhir-resource". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|--------------|:--------:|:------------:|------------------------------------------------------------------|
|
||||
| resourceType | string | true | The FHIR resource type to retrieve (e.g., Patient, Observation). |
|
||||
| resourceID | string | true | The ID of the FHIR resource to retrieve. |
|
||||
| storeID | string | true* | The FHIR store ID to retrieve the resource from. |
|
||||
|
||||
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "cloud-healthcare-get-fhir-store-metrics"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-get-fhir-store-metrics" tool retrieves metrics for a FHIR store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-get-fhir-store-metrics
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-get-fhir-store-metrics` tool retrieves metrics for a FHIR store. It's
|
||||
compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-get-fhir-store-metrics` returns the metrics of a FHIR store.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
get_fhir_store_metrics:
|
||||
kind: cloud-healthcare-get-fhir-store-metrics
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to get metrics for a FHIR store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-get-fhir-store-metrics". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-----------|:--------:|:------------:|---------------------------------------|
|
||||
| storeID | string | true* | The FHIR store ID to get metrics for. |
|
||||
|
||||
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "cloud-healthcare-get-fhir-store"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-get-fhir-store" tool retrieves information about a FHIR store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-get-fhir-store
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-get-fhir-store` tool retrieves information about a FHIR store. It's
|
||||
compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-get-fhir-store` returns the details of a FHIR store.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
get_fhir_store:
|
||||
kind: cloud-healthcare-get-fhir-store
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to get information about a FHIR store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-get-fhir-store". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-----------|:--------:|:------------:|---------------------------------------|
|
||||
| storeID | string | true* | The FHIR store ID to get details for. |
|
||||
|
||||
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "cloud-healthcare-list-dicom-stores"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-list-dicom-stores" lists the available DICOM stores in the healthcare dataset.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-list-dicom-stores
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-list-dicom-stores` lists the available DICOM stores in the healthcare dataset.
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-list-dicom-stores` returns the details of the available DICOM stores in the
|
||||
dataset of the healthcare source. It takes no extra parameters.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
list_dicom_stores:
|
||||
kind: cloud-healthcare-list-dicom-stores
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to list DICOM stores in the healthcare dataset.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-list-dicom-stores". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
title: "cloud-healthcare-list-fhir-stores"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-list-fhir-stores" lists the available FHIR stores in the healthcare dataset.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-list-fhir-stores
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-list-fhir-stores` lists the available FHIR stores in the healthcare dataset.
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-list-fhir-stores` returns the details of the available FHIR stores in the
|
||||
dataset of the healthcare source. It takes no extra parameters.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
list_fhir_stores:
|
||||
kind: cloud-healthcare-list-fhir-stores
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to list FHIR stores in the healthcare dataset.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-list-fhir-stores". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: "cloud-healthcare-retrieve-rendered-dicom-instance"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-retrieve-rendered-dicom-instance" tool retrieves a rendered DICOM instance from a DICOM store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-retrieve-rendered-dicom-instance
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-retrieve-rendered-dicom-instance` tool retrieves a rendered DICOM instance from a DICOM store.
|
||||
It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-retrieve-rendered-dicom-instance` returns a base64 encoded string of the image in JPEG format.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
retrieve_rendered_dicom_instance:
|
||||
kind: cloud-healthcare-retrieve-rendered-dicom-instance
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to retrieve a rendered DICOM instance from the DICOM store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|--------------------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-retrieve-rendered-dicom-instance". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------------|:--------:|:------------:|--------------------------------------------------------------------------------------------------|
|
||||
| StudyInstanceUID | string | true | The UID of the DICOM study. |
|
||||
| SeriesInstanceUID | string | true | The UID of the DICOM series. |
|
||||
| SOPInstanceUID | string | true | The UID of the SOP instance. |
|
||||
| FrameNumber | integer | false | The frame number to retrieve (1-based). Only applicable to multi-frame instances. Defaults to 1. |
|
||||
| storeID | string | true* | The DICOM store ID to retrieve from. |
|
||||
|
||||
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "cloud-healthcare-search-dicom-instances"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-search-dicom-instances" tool searches for DICOM instances in a DICOM store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-search-dicom-instances
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-search-dicom-instances` tool searches for DICOM instances in a DICOM store based on a
|
||||
set of criteria. It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`search-dicom-instances` returns a list of DICOM instances that match the given criteria.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
search_dicom_instances:
|
||||
kind: cloud-healthcare-search-dicom-instances
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to search for DICOM instances in the DICOM store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-search-dicom-instances". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|------------------------|:--------:|:------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| StudyInstanceUID | string | false | The UID of the DICOM study. |
|
||||
| PatientName | string | false | The name of the patient. |
|
||||
| PatientID | string | false | The ID of the patient. |
|
||||
| AccessionNumber | string | false | The accession number of the study. |
|
||||
| ReferringPhysicianName | string | false | The name of the referring physician. |
|
||||
| StudyDate | string | false | The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`. |
|
||||
| SeriesInstanceUID | string | false | The UID of the DICOM series. |
|
||||
| Modality | string | false | The modality of the series. |
|
||||
| SOPInstanceUID | string | false | The UID of the SOP instance. |
|
||||
| fuzzymatching | boolean | false | Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match. |
|
||||
| includefield | []string | false | List of attributeIDs to include in the output, such as DICOM tag IDs or keywords. Set to `["all"]` to return all available tags. |
|
||||
| storeID | string | true* | The DICOM store ID to search in. |
|
||||
|
||||
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
title: "cloud-healthcare-search-dicom-series"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-search-dicom-series" tool searches for DICOM series in a DICOM store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-search-dicom-series
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-search-dicom-series` tool searches for DICOM series in a DICOM store based on a
|
||||
set of criteria. It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-search-dicom-series` returns a list of DICOM series that match the given criteria.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
search_dicom_series:
|
||||
kind: cloud-healthcare-search-dicom-series
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to search for DICOM series in the DICOM store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-search-dicom-series". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|------------------------|:--------:|:------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| StudyInstanceUID | string | false | The UID of the DICOM study. |
|
||||
| PatientName | string | false | The name of the patient. |
|
||||
| PatientID | string | false | The ID of the patient. |
|
||||
| AccessionNumber | string | false | The accession number of the study. |
|
||||
| ReferringPhysicianName | string | false | The name of the referring physician. |
|
||||
| StudyDate | string | false | The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`. |
|
||||
| SeriesInstanceUID | string | false | The UID of the DICOM series. |
|
||||
| Modality | string | false | The modality of the series. |
|
||||
| fuzzymatching | boolean | false | Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match. |
|
||||
| includefield | []string | false | List of attributeIDs to include in the output, such as DICOM tag IDs or keywords. Set to `["all"]` to return all available tags. |
|
||||
| storeID | string | true* | The DICOM store ID to search in. |
|
||||
|
||||
*If the `allowedDIComStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: "cloud-healthcare-search-dicom-studies"
|
||||
type: docs
|
||||
weight: 1
|
||||
description: >
|
||||
A "cloud-healthcare-search-dicom-studies" tool searches for DICOM studies in a DICOM store.
|
||||
aliases:
|
||||
- /resources/tools/cloud-healthcare-healthcare-search-dicom-studies
|
||||
---
|
||||
|
||||
## About
|
||||
|
||||
A `cloud-healthcare-search-dicom-studies` tool searches for DICOM studies in a DICOM store based on a
|
||||
set of criteria. It's compatible with the following sources:
|
||||
|
||||
- [cloud-healthcare](../../sources/cloud-healthcare.md)
|
||||
|
||||
`cloud-healthcare-search-dicom-studies` returns a list of DICOM studies that match the given criteria.
|
||||
|
||||
## Example
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
search_dicom_studies:
|
||||
kind: cloud-healthcare-search-dicom-studies
|
||||
source: my-healthcare-source
|
||||
description: Use this tool to search for DICOM studies in the DICOM store.
|
||||
```
|
||||
|
||||
## Reference
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|-------------|:--------:|:------------:|----------------------------------------------------|
|
||||
| kind | string | true | Must be "cloud-healthcare-search-dicom-studies". |
|
||||
| source | string | true | Name of the healthcare source. |
|
||||
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||
|
||||
### Parameters
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|------------------------|:--------:|:------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| StudyInstanceUID | string | false | The UID of the DICOM study. |
|
||||
| PatientName | string | false | The name of the patient. |
|
||||
| PatientID | string | false | The ID of the patient. |
|
||||
| AccessionNumber | string | false | The accession number of the study. |
|
||||
| ReferringPhysicianName | string | false | The name of the referring physician. |
|
||||
| StudyDate | string | false | The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`. |
|
||||
| fuzzymatching | boolean | false | Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match. |
|
||||
| includefield | []string | false | List of attributeIDs to include in the output, such as DICOM tag IDs or keywords. Set to `["all"]` to return all available tags. |
|
||||
| storeID | string | true* | The DICOM store ID to search in. |
|
||||
|
||||
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.
|
||||
@@ -26,6 +26,7 @@ var expectedToolSources = []string{
|
||||
"alloydb-postgres",
|
||||
"bigquery",
|
||||
"clickhouse",
|
||||
"cloud-healthcare",
|
||||
"cloud-sql-mssql-admin",
|
||||
"cloud-sql-mssql-observability",
|
||||
"cloud-sql-mssql",
|
||||
@@ -125,6 +126,7 @@ func TestGetPrebuiltTool(t *testing.T) {
|
||||
mindsdb_config, _ := Get("mindsdb")
|
||||
sqlite_config, _ := Get("sqlite")
|
||||
neo4jconfig, _ := Get("neo4j")
|
||||
healthcare_config, _ := Get("cloud-healthcare")
|
||||
|
||||
if len(alloydb_admin_config) <= 0 {
|
||||
t.Fatalf("unexpected error: could not fetch alloydb prebuilt tools yaml")
|
||||
@@ -213,6 +215,9 @@ func TestGetPrebuiltTool(t *testing.T) {
|
||||
if len(neo4jconfig) <= 0 {
|
||||
t.Fatalf("unexpected error: could not fetch neo4j prebuilt tools yaml")
|
||||
}
|
||||
if len(healthcare_config) <= 0 {
|
||||
t.Fatalf("unexpected error: could not fetch healthcare prebuilt tools yaml")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailGetPrebuiltTool(t *testing.T) {
|
||||
|
||||
103
internal/prebuiltconfigs/tools/cloud-healthcare.yaml
Normal file
103
internal/prebuiltconfigs/tools/cloud-healthcare.yaml
Normal file
@@ -0,0 +1,103 @@
|
||||
# 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.
|
||||
|
||||
sources:
|
||||
healthcare-source:
|
||||
kind: cloud-healthcare
|
||||
project: ${CLOUD_HEALTHCARE_PROJECT}
|
||||
region: ${CLOUD_HEALTHCARE_REGION}
|
||||
dataset: ${CLOUD_HEALTHCARE_DATASET}
|
||||
useClientOAuth: ${CLOUD_HEALTHCARE_USE_CLIENT_OAUTH:false}
|
||||
|
||||
tools:
|
||||
get_dataset:
|
||||
kind: cloud-healthcare-get-dataset
|
||||
description: Use this tool to get the details of a healthcare dataset
|
||||
source: healthcare-source
|
||||
list_dicom_stores:
|
||||
kind: cloud-healthcare-list-dicom-stores
|
||||
description: Use this tool to list the DICOM stores in the healthcare dataset
|
||||
source: healthcare-source
|
||||
list_fhir_stores:
|
||||
kind: cloud-healthcare-list-fhir-stores
|
||||
description: Use this tool to list the FHIR stores in the healthcare dataset
|
||||
source: healthcare-source
|
||||
get_fhir_store:
|
||||
kind: cloud-healthcare-get-fhir-store
|
||||
description: Use this tool to get details about a FHIR store in the healthcare dataset
|
||||
source: healthcare-source
|
||||
get_fhir_store_metrics:
|
||||
kind: cloud-healthcare-get-fhir-store-metrics
|
||||
description: Use this tool to get metrics about a FHIR store in the healthcare dataset
|
||||
source: healthcare-source
|
||||
get_fhir_resource:
|
||||
kind: cloud-healthcare-get-fhir-resource
|
||||
description: Use this tool to get a FHIR resource from a FHIR store
|
||||
source: healthcare-source
|
||||
fhir_patient_search:
|
||||
kind: cloud-healthcare-fhir-patient-search
|
||||
description: Use this tool to search for patient resource(s) in a FHIR store based on a set of criteria
|
||||
source: healthcare-source
|
||||
fhir_patient_everything:
|
||||
kind: cloud-healthcare-fhir-patient-everything
|
||||
description: Use this tool to retrieve resources related to a given patient from a FHIR store
|
||||
source: healthcare-source
|
||||
fhir_fetch_page:
|
||||
kind: cloud-healthcare-fhir-fetch-page
|
||||
description: Use this tool to fetche a page of FHIR resources from a FHIR store
|
||||
source: healthcare-source
|
||||
get_dicom_store:
|
||||
kind: cloud-healthcare-get-dicom-store
|
||||
description: Use this tool to get details about a DICOM store in the healthcare dataset
|
||||
source: healthcare-source
|
||||
get_dicom_store_metrics:
|
||||
kind: cloud-healthcare-get-dicom-store-metrics
|
||||
description: Use this tool to get metrics about a DICOM store in the healthcare dataset
|
||||
source: healthcare-source
|
||||
search_dicom_studies:
|
||||
kind: cloud-healthcare-search-dicom-studies
|
||||
description: Use this tool to search for DICOM studies in a DICOM store
|
||||
source: healthcare-source
|
||||
search_dicom_series:
|
||||
kind: cloud-healthcare-search-dicom-series
|
||||
description: Use this tool to search for DICOM series in a DICOM store
|
||||
source: healthcare-source
|
||||
search_dicom_instances:
|
||||
kind: cloud-healthcare-search-dicom-instances
|
||||
description: Use this tool to search for DICOM instances in a DICOM store
|
||||
source: healthcare-source
|
||||
retrieve_rendered_dicom_instance:
|
||||
kind: cloud-healthcare-retrieve-rendered-dicom-instance
|
||||
description: Use this tool to retrieve a base64 encoding of a rendered DICOM instance from a DICOM store in JPEG format
|
||||
source: healthcare-source
|
||||
|
||||
toolsets:
|
||||
cloud_healthcare_dataset_tools:
|
||||
- get_dataset
|
||||
- list_dicom_stores
|
||||
- list_fhir_stores
|
||||
cloud_healthcare_fhir_tools:
|
||||
- get_fhir_store
|
||||
- get_fhir_store_metrics
|
||||
- get_fhir_resource
|
||||
- fhir_patient_search
|
||||
- fhir_patient_everything
|
||||
- fhir_fetch_page
|
||||
cloud_healthcare_dicom_tools:
|
||||
- get_dicom_store
|
||||
- get_dicom_store_metrics
|
||||
- search_dicom_studies
|
||||
- search_dicom_series
|
||||
- search_dicom_instances
|
||||
- retrieve_rendered_dicom_instance
|
||||
263
internal/sources/cloudhealthcare/cloud_healthcare.go
Normal file
263
internal/sources/cloudhealthcare/cloud_healthcare.go
Normal file
@@ -0,0 +1,263 @@
|
||||
// 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 cloudhealthcare
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
const SourceKind string = "cloud-healthcare"
|
||||
|
||||
// validate interface
|
||||
var _ sources.SourceConfig = Config{}
|
||||
|
||||
type HealthcareServiceCreator func(tokenString string) (*healthcare.Service, error)
|
||||
|
||||
func init() {
|
||||
if !sources.Register(SourceKind, newConfig) {
|
||||
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
|
||||
}
|
||||
}
|
||||
|
||||
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
|
||||
actual := Config{Name: name}
|
||||
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return actual, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// Healthcare configs
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Project string `yaml:"project" validate:"required"`
|
||||
Region string `yaml:"region" validate:"required"`
|
||||
Dataset string `yaml:"dataset" validate:"required"`
|
||||
AllowedFHIRStores []string `yaml:"allowedFhirStores"`
|
||||
AllowedDICOMStores []string `yaml:"allowedDicomStores"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
}
|
||||
|
||||
func (c Config) SourceConfigKind() string {
|
||||
return SourceKind
|
||||
}
|
||||
|
||||
func (c Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
|
||||
var service *healthcare.Service
|
||||
var serviceCreator HealthcareServiceCreator
|
||||
var tokenSource oauth2.TokenSource
|
||||
|
||||
svc, tok, err := initHealthcareConnection(ctx, tracer, c.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from ADC: %w", err)
|
||||
}
|
||||
if c.UseClientOAuth {
|
||||
serviceCreator, err = newHealthcareServiceCreator(ctx, tracer, c.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error constructing service creator: %w", err)
|
||||
}
|
||||
} else {
|
||||
service = svc
|
||||
tokenSource = tok
|
||||
}
|
||||
|
||||
dsName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", c.Project, c.Region, c.Dataset)
|
||||
if _, err = svc.Projects.Locations.Datasets.FhirStores.Get(dsName).Do(); err != nil {
|
||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound {
|
||||
return nil, fmt.Errorf("dataset '%s' not found", dsName)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to verify existence of dataset '%s': %w", dsName, err)
|
||||
}
|
||||
|
||||
allowedFHIRStores := make(map[string]struct{})
|
||||
for _, store := range c.AllowedFHIRStores {
|
||||
name := fmt.Sprintf("%s/fhirStores/%s", dsName, store)
|
||||
_, err := svc.Projects.Locations.Datasets.FhirStores.Get(name).Do()
|
||||
if err != nil {
|
||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound {
|
||||
return nil, fmt.Errorf("allowedFhirStore '%s' not found in dataset '%s'", store, dsName)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to verify allowedFhirStore '%s' in datasest '%s': %w", store, dsName, err)
|
||||
}
|
||||
allowedFHIRStores[store] = struct{}{}
|
||||
}
|
||||
allowedDICOMStores := make(map[string]struct{})
|
||||
for _, store := range c.AllowedDICOMStores {
|
||||
name := fmt.Sprintf("%s/dicomStores/%s", dsName, store)
|
||||
_, err := svc.Projects.Locations.Datasets.DicomStores.Get(name).Do()
|
||||
if err != nil {
|
||||
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound {
|
||||
return nil, fmt.Errorf("allowedDicomStore '%s' not found in dataset '%s'", store, dsName)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to verify allowedDicomFhirStore '%s' in datasest '%s': %w", store, dsName, err)
|
||||
}
|
||||
allowedDICOMStores[store] = struct{}{}
|
||||
}
|
||||
s := &Source{
|
||||
name: c.Name,
|
||||
kind: SourceKind,
|
||||
project: c.Project,
|
||||
region: c.Region,
|
||||
dataset: c.Dataset,
|
||||
service: service,
|
||||
serviceCreator: serviceCreator,
|
||||
tokenSource: tokenSource,
|
||||
allowedFHIRStores: allowedFHIRStores,
|
||||
allowedDICOMStores: allowedDICOMStores,
|
||||
useClientOAuth: c.UseClientOAuth,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func newHealthcareServiceCreator(ctx context.Context, tracer trace.Tracer, name string) (func(string) (*healthcare.Service, error), error) {
|
||||
userAgent, err := util.UserAgentFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func(tokenString string) (*healthcare.Service, error) {
|
||||
return initHealthcareConnectionWithOAuthToken(ctx, tracer, name, userAgent, tokenString)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func initHealthcareConnectionWithOAuthToken(ctx context.Context, tracer trace.Tracer, name string, userAgent string, tokenString string) (*healthcare.Service, error) {
|
||||
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
|
||||
defer span.End()
|
||||
// Construct token source
|
||||
token := &oauth2.Token{
|
||||
AccessToken: string(tokenString),
|
||||
}
|
||||
ts := oauth2.StaticTokenSource(token)
|
||||
|
||||
// Initialize the Healthcare service with tokenSource
|
||||
service, err := healthcare.NewService(ctx, option.WithUserAgent(userAgent), option.WithTokenSource(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Healthcare service: %w", err)
|
||||
}
|
||||
service.UserAgent = userAgent
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func initHealthcareConnection(ctx context.Context, tracer trace.Tracer, name string) (*healthcare.Service, oauth2.TokenSource, error) {
|
||||
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
|
||||
defer span.End()
|
||||
|
||||
cred, err := google.FindDefaultCredentials(ctx, healthcare.CloudHealthcareScope)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to find default Google Cloud credentials with scope %q: %w", healthcare.CloudHealthcareScope, err)
|
||||
}
|
||||
|
||||
userAgent, err := util.UserAgentFromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
service, err := healthcare.NewService(ctx, option.WithUserAgent(userAgent), option.WithCredentials(cred))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create Healthcare service: %w", err)
|
||||
}
|
||||
service.UserAgent = userAgent
|
||||
return service, cred.TokenSource, nil
|
||||
}
|
||||
|
||||
var _ sources.Source = &Source{}
|
||||
|
||||
type Source struct {
|
||||
name string `yaml:"name"`
|
||||
kind string `yaml:"kind"`
|
||||
project string
|
||||
region string
|
||||
dataset string
|
||||
service *healthcare.Service
|
||||
serviceCreator HealthcareServiceCreator
|
||||
tokenSource oauth2.TokenSource
|
||||
allowedFHIRStores map[string]struct{}
|
||||
allowedDICOMStores map[string]struct{}
|
||||
useClientOAuth bool
|
||||
}
|
||||
|
||||
func (s *Source) SourceKind() string {
|
||||
return SourceKind
|
||||
}
|
||||
|
||||
func (s *Source) Project() string {
|
||||
return s.project
|
||||
}
|
||||
|
||||
func (s *Source) Region() string {
|
||||
return s.region
|
||||
}
|
||||
|
||||
func (s *Source) DatasetID() string {
|
||||
return s.dataset
|
||||
}
|
||||
|
||||
func (s *Source) Service() *healthcare.Service {
|
||||
return s.service
|
||||
}
|
||||
|
||||
func (s *Source) ServiceCreator() HealthcareServiceCreator {
|
||||
return s.serviceCreator
|
||||
}
|
||||
|
||||
func (s *Source) TokenSource() oauth2.TokenSource {
|
||||
return s.tokenSource
|
||||
}
|
||||
|
||||
func (s *Source) AllowedFHIRStores() map[string]struct{} {
|
||||
if len(s.allowedFHIRStores) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.allowedFHIRStores
|
||||
}
|
||||
|
||||
func (s *Source) AllowedDICOMStores() map[string]struct{} {
|
||||
if len(s.allowedDICOMStores) == 0 {
|
||||
return nil
|
||||
}
|
||||
return s.allowedDICOMStores
|
||||
}
|
||||
|
||||
func (s *Source) IsFHIRStoreAllowed(storeID string) bool {
|
||||
if len(s.allowedFHIRStores) == 0 {
|
||||
return true
|
||||
}
|
||||
_, ok := s.allowedFHIRStores[storeID]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *Source) IsDICOMStoreAllowed(storeID string) bool {
|
||||
if len(s.allowedDICOMStores) == 0 {
|
||||
return true
|
||||
}
|
||||
_, ok := s.allowedDICOMStores[storeID]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *Source) UseClientAuthorization() bool {
|
||||
return s.useClientOAuth
|
||||
}
|
||||
168
internal/sources/cloudhealthcare/cloud_healthcare_test.go
Normal file
168
internal/sources/cloudhealthcare/cloud_healthcare_test.go
Normal file
@@ -0,0 +1,168 @@
|
||||
// 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 cloudhealthcare_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
)
|
||||
|
||||
func TestParseFromYamlCloudHealthcare(t *testing.T) {
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.SourceConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
sources:
|
||||
my-instance:
|
||||
kind: cloud-healthcare
|
||||
project: my-project
|
||||
region: us-central1
|
||||
dataset: my-dataset
|
||||
`,
|
||||
want: server.SourceConfigs{
|
||||
"my-instance": cloudhealthcare.Config{
|
||||
Name: "my-instance",
|
||||
Kind: cloudhealthcare.SourceKind,
|
||||
Project: "my-project",
|
||||
Region: "us-central1",
|
||||
Dataset: "my-dataset",
|
||||
UseClientOAuth: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "use client auth example",
|
||||
in: `
|
||||
sources:
|
||||
my-instance:
|
||||
kind: cloud-healthcare
|
||||
project: my-project
|
||||
region: us
|
||||
dataset: my-dataset
|
||||
useClientOAuth: true
|
||||
`,
|
||||
want: server.SourceConfigs{
|
||||
"my-instance": cloudhealthcare.Config{
|
||||
Name: "my-instance",
|
||||
Kind: cloudhealthcare.SourceKind,
|
||||
Project: "my-project",
|
||||
Region: "us",
|
||||
Dataset: "my-dataset",
|
||||
UseClientOAuth: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with allowed stores example",
|
||||
in: `
|
||||
sources:
|
||||
my-instance:
|
||||
kind: cloud-healthcare
|
||||
project: my-project
|
||||
region: us
|
||||
dataset: my-dataset
|
||||
allowedFhirStores:
|
||||
- my-fhir-store
|
||||
allowedDicomStores:
|
||||
- my-dicom-store1
|
||||
- my-dicom-store2
|
||||
`,
|
||||
want: server.SourceConfigs{
|
||||
"my-instance": cloudhealthcare.Config{
|
||||
Name: "my-instance",
|
||||
Kind: cloudhealthcare.SourceKind,
|
||||
Project: "my-project",
|
||||
Region: "us",
|
||||
Dataset: "my-dataset",
|
||||
AllowedFHIRStores: []string{"my-fhir-store"},
|
||||
AllowedDICOMStores: []string{"my-dicom-store1", "my-dicom-store2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Sources server.SourceConfigs `yaml:"sources"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if !cmp.Equal(tc.want, got.Sources) {
|
||||
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFailParseFromYaml(t *testing.T) {
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
desc: "extra field",
|
||||
in: `
|
||||
sources:
|
||||
my-instance:
|
||||
kind: cloud-healthcare
|
||||
project: my-project
|
||||
region: us-central1
|
||||
dataset: my-dataset
|
||||
foo: bar
|
||||
`,
|
||||
err: "unable to parse source \"my-instance\" as \"cloud-healthcare\": [2:1] unknown field \"foo\"\n 1 | dataset: my-dataset\n> 2 | foo: bar\n ^\n 3 | kind: cloud-healthcare\n 4 | project: my-project\n 5 | region: us-central1",
|
||||
},
|
||||
{
|
||||
desc: "missing required field",
|
||||
in: `
|
||||
sources:
|
||||
my-instance:
|
||||
kind: cloud-healthcare
|
||||
project: my-project
|
||||
region: us-central1
|
||||
`,
|
||||
err: `unable to parse source "my-instance" as "cloud-healthcare": Key: 'Config.Dataset' Error:Field validation for 'Dataset' failed on the 'required' tag`,
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Sources server.SourceConfigs `yaml:"sources"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
|
||||
if err == nil {
|
||||
t.Fatalf("expect parsing to fail")
|
||||
}
|
||||
errStr := err.Error()
|
||||
if errStr != tc.err {
|
||||
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// 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 fhirfetchpage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-fhir-fetch-page"
|
||||
const (
|
||||
pageURLKey = "pageURL"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedFHIRStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
urlParameter := tools.NewStringParameter(pageURLKey, "The full URL of the FHIR page to fetch. This would be the value of `Bundle.entry.link.url` field within the response returned from FHIR search or FHIR patient everything operations.")
|
||||
parameters := tools.Parameters{urlParameter}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedFHIRStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
url, ok := params.AsMap()[pageURLKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", pageURLKey)
|
||||
}
|
||||
|
||||
var httpClient *http.Client
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tokenStr})
|
||||
httpClient = oauth2.NewClient(ctx, ts)
|
||||
} else {
|
||||
// The t.Service object holds a client with the default credentials.
|
||||
// However, the client is not exported, so we have to create a new one.
|
||||
var err error
|
||||
httpClient, err = google.DefaultClient(ctx, healthcare.CloudHealthcareScope)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create default http client: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create http request: %w", err)
|
||||
}
|
||||
req.Header.Set("Accept", "application/fhir+json;charset=utf-8")
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get fhir page from %q: %w", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("read: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
|
||||
}
|
||||
var jsonMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal response as json: %w", err)
|
||||
}
|
||||
return jsonMap, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 fhirfetchpage_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
fhirfetchpage "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirfetchpage"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareFHIRFetchPage(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-fhir-fetch-page
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": fhirfetchpage.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-fhir-fetch-page",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
// 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 fhirpatienteverything
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-fhir-patient-everything"
|
||||
const (
|
||||
patientIDKey = "patientID"
|
||||
typeFilterKey = "resourceTypesFilter"
|
||||
sinceFilterKey = "sinceFilter"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedFHIRStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
idParameter := tools.NewStringParameter(patientIDKey, "The ID of the patient FHIR resource for which the information is required")
|
||||
typeFilterParameter := tools.NewArrayParameterWithDefault(typeFilterKey, []any{}, "List of FHIR resource types. If provided, only resources of the specified resource type(s) are returned.", tools.NewStringParameter("resourceType", "A FHIR resource type"))
|
||||
sinceFilterParameter := tools.NewStringParameterWithDefault(sinceFilterKey, "", "If provided, only resources updated after this time are returned. The time uses the format YYYY-MM-DDThh:mm:ss.sss+zz:zz. The time must be specified to the second and include a time zone. For example, 2015-02-07T13:28:17.239+02:00 or 2017-01-01T00:00:00Z")
|
||||
parameters := tools.Parameters{idParameter, typeFilterParameter, sinceFilterParameter}
|
||||
if len(s.AllowedFHIRStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to retrieve the resource from."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedFHIRStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patientID, ok := params.AsMap()[patientIDKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", patientIDKey)
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s/fhir/Patient/%s", t.Project, t.Region, t.Dataset, storeID, patientID)
|
||||
var opts []googleapi.CallOption
|
||||
if val, ok := params.AsMap()[typeFilterKey]; ok {
|
||||
types, ok := val.([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string array", typeFilterKey)
|
||||
}
|
||||
typeFilterSlice, err := tools.ConvertAnySliceToTyped(types, "string")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't convert '%s' to array of strings: %s", typeFilterKey, err)
|
||||
}
|
||||
if len(typeFilterSlice.([]string)) != 0 {
|
||||
opts = append(opts, googleapi.QueryParameter("_type", strings.Join(typeFilterSlice.([]string), ",")))
|
||||
}
|
||||
}
|
||||
if since, ok := params.AsMap()[sinceFilterKey]; ok {
|
||||
sinceStr, ok := since.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", sinceFilterKey)
|
||||
}
|
||||
if sinceStr != "" {
|
||||
opts = append(opts, googleapi.QueryParameter("_since", sinceStr))
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := svc.Projects.Locations.Datasets.FhirStores.Fhir.PatientEverything(name).Do(opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to call patient everything for %q: %w", name, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("patient-everything: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
|
||||
}
|
||||
var jsonMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal response as json: %w", err)
|
||||
}
|
||||
return jsonMap, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 fhirpatienteverything_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
fhirpatienteverything "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirpatienteverything"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareFHIRPatientEverything(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-fhir-patient-everything
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": fhirpatienteverything.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-fhir-patient-everything",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
// 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 fhirpatientsearch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-fhir-patient-search"
|
||||
const (
|
||||
activeKey = "active"
|
||||
cityKey = "city"
|
||||
countryKey = "country"
|
||||
postalCodeKey = "postalcode"
|
||||
stateKey = "state"
|
||||
addressSubstringKey = "addressSubstring"
|
||||
birthDateRangeKey = "birthDateRange"
|
||||
deathDateRangeKey = "deathDateRange"
|
||||
deceasedKey = "deceased"
|
||||
emailKey = "email"
|
||||
genderKey = "gender"
|
||||
addressUseKey = "addressUse"
|
||||
nameKey = "name"
|
||||
givenNameKey = "givenName"
|
||||
familyNameKey = "familyName"
|
||||
phoneKey = "phone"
|
||||
languageKey = "language"
|
||||
identifierKey = "identifier"
|
||||
summaryKey = "summary"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedFHIRStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{
|
||||
tools.NewStringParameterWithDefault(activeKey, "", "Whether the patient record is active. Use true or false"),
|
||||
tools.NewStringParameterWithDefault(cityKey, "", "The city of the patient's address"),
|
||||
tools.NewStringParameterWithDefault(countryKey, "", "The country of the patient's address"),
|
||||
tools.NewStringParameterWithDefault(postalCodeKey, "", "The postal code of the patient's address"),
|
||||
tools.NewStringParameterWithDefault(stateKey, "", "The state of the patient's address"),
|
||||
tools.NewStringParameterWithDefault(addressSubstringKey, "", "A substring to search for in any address field"),
|
||||
tools.NewStringParameterWithDefault(birthDateRangeKey, "", "A date range for the patient's birthdate in the format YYYY-MM-DD/YYYY-MM-DD. Omit the first or second date to indicate open-ended ranges (e.g. '/2000-01-01' or '1950-01-01/')"),
|
||||
tools.NewStringParameterWithDefault(deathDateRangeKey, "", "A date range for the patient's death date in the format YYYY-MM-DD/YYYY-MM-DD. Omit the first or second date to indicate open-ended ranges (e.g. '/2000-01-01' or '1950-01-01/')"),
|
||||
tools.NewStringParameterWithDefault(deceasedKey, "", "Whether the patient is deceased. Use true or false"),
|
||||
tools.NewStringParameterWithDefault(emailKey, "", "The patient's email address"),
|
||||
tools.NewStringParameterWithDefault(genderKey, "", "The patient's gender. Must be one of 'male', 'female', 'other', or 'unknown'"),
|
||||
tools.NewStringParameterWithDefault(addressUseKey, "", "The use of the patient's address. Must be one of 'home', 'work', 'temp', 'old', or 'billing'"),
|
||||
tools.NewStringParameterWithDefault(nameKey, "", "The patient's name. Can be a family name, given name, or both"),
|
||||
tools.NewStringParameterWithDefault(givenNameKey, "", "A portion of the given name of the patient"),
|
||||
tools.NewStringParameterWithDefault(familyNameKey, "", "A portion of the family name of the patient"),
|
||||
tools.NewStringParameterWithDefault(phoneKey, "", "The patient's phone number"),
|
||||
tools.NewStringParameterWithDefault(languageKey, "", "The patient's preferred language. Must be a valid BCP-47 code (e.g. 'en-US', 'es')"),
|
||||
tools.NewStringParameterWithDefault(identifierKey, "", "An identifier for the patient"),
|
||||
tools.NewBooleanParameterWithDefault(summaryKey, true, "Requests the server to return a subset of the resource. Return a limited subset of elements from the resource. Enabled by default to reduce response size. Use get-fhir-resource tool to get full resource details (preferred) or set to false to disable."),
|
||||
}
|
||||
|
||||
if len(s.AllowedFHIRStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to retrieve the resource from."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedFHIRStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var summary bool
|
||||
var opts []googleapi.CallOption
|
||||
for k, v := range params.AsMap() {
|
||||
if k == common.StoreKey {
|
||||
continue
|
||||
}
|
||||
if k == summaryKey {
|
||||
var ok bool
|
||||
summary, ok = v.(bool)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a boolean", summaryKey)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
val, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid parameter '%s'; expected a string", k)
|
||||
}
|
||||
if val == "" {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case activeKey, deceasedKey, emailKey, genderKey, phoneKey, languageKey, identifierKey:
|
||||
opts = append(opts, googleapi.QueryParameter(k, val))
|
||||
case cityKey, countryKey, postalCodeKey, stateKey:
|
||||
opts = append(opts, googleapi.QueryParameter("address-"+k, val))
|
||||
case addressSubstringKey:
|
||||
opts = append(opts, googleapi.QueryParameter("address", val))
|
||||
case birthDateRangeKey, deathDateRangeKey:
|
||||
key := "birthdate"
|
||||
if k == deathDateRangeKey {
|
||||
key = "death-date"
|
||||
}
|
||||
parts := strings.Split(val, "/")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid '%s' format; expected YYYY-MM-DD/YYYY-MM-DD", k)
|
||||
}
|
||||
var values []string
|
||||
if parts[0] != "" {
|
||||
values = append(values, "ge"+parts[0])
|
||||
}
|
||||
if parts[1] != "" {
|
||||
values = append(values, "le"+parts[1])
|
||||
}
|
||||
if len(values) != 0 {
|
||||
opts = append(opts, googleapi.QueryParameter(key, values...))
|
||||
}
|
||||
case addressUseKey:
|
||||
opts = append(opts, googleapi.QueryParameter("address-use", val))
|
||||
case nameKey:
|
||||
parts := strings.Split(val, " ")
|
||||
for _, part := range parts {
|
||||
opts = append(opts, googleapi.QueryParameter("name", part))
|
||||
}
|
||||
case givenNameKey:
|
||||
opts = append(opts, googleapi.QueryParameter("given", val))
|
||||
case familyNameKey:
|
||||
opts = append(opts, googleapi.QueryParameter("family", val))
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected parameter key %q", k)
|
||||
}
|
||||
}
|
||||
if summary {
|
||||
opts = append(opts, googleapi.QueryParameter("_summary", "text"))
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
resp, err := svc.Projects.Locations.Datasets.FhirStores.Fhir.SearchType(name, "Patient", &healthcare.SearchResourcesRequest{ResourceType: "Patient"}).Do(opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search patient resources: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
|
||||
}
|
||||
var jsonMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal response as json: %w", err)
|
||||
}
|
||||
return jsonMap, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 fhirpatientsearch_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
fhirpatientsearch "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirpatientsearch"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareFHIRPatientSearch(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-fhir-patient-search
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": fhirpatientsearch.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-fhir-patient-search",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// 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 gethealthcaredataset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-get-dataset"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
svc := t.Service
|
||||
var err error
|
||||
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", t.Project, t.Region, t.Dataset)
|
||||
dataset, err := svc.Projects.Locations.Datasets.Get(datasetName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err)
|
||||
}
|
||||
return dataset, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 gethealthcaredataset_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
getdataset "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdataset"
|
||||
)
|
||||
|
||||
func TestParseFromYamlGetHealthcareDataset(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-get-dataset
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": getdataset.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-get-dataset",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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 getdicomstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-get-dicom-store"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedDICOMStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{}
|
||||
if len(s.AllowedDICOMStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedDICOMStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
store, err := svc.Projects.Locations.Datasets.DicomStores.Get(storeName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get DICOM store %q: %w", storeName, err)
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 getdicomstore_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
getdicomstore "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstore"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareGetDICOMStore(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-get-dicom-store
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": getdicomstore.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-get-dicom-store",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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 getdicomstoremetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-get-dicom-store-metrics"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedDICOMStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{}
|
||||
if len(s.AllowedDICOMStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get metrics for."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedDICOMStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
store, err := svc.Projects.Locations.Datasets.DicomStores.GetDICOMStoreMetrics(storeName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get metrics for DICOM store %q: %w", storeName, err)
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 getdicomstoremetrics_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
getdicomstoremetrics "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstoremetrics"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareGetDICOMStoreMetrics(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-get-dicom-store-metrics
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": getdicomstoremetrics.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-get-dicom-store-metrics",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// 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 getfhirresource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-get-fhir-resource"
|
||||
const (
|
||||
typeKey = "resourceType"
|
||||
idKey = "resourceID"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedFHIRStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
typeParameter := tools.NewStringParameter(typeKey, "The FHIR resource type to retrieve (e.g., Patient, Observation).")
|
||||
idParameter := tools.NewStringParameter(idKey, "The ID of the FHIR resource to retrieve.")
|
||||
parameters := tools.Parameters{typeParameter, idParameter}
|
||||
if len(s.AllowedFHIRStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to retrieve the resource from."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedFHIRStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resType, ok := params.AsMap()[typeKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", typeKey)
|
||||
}
|
||||
|
||||
resID, ok := params.AsMap()[idKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", idKey)
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s/fhir/%s/%s", t.Project, t.Region, t.Dataset, storeID, resType, resID)
|
||||
call := svc.Projects.Locations.Datasets.FhirStores.Fhir.Read(name)
|
||||
call.Header().Set("Content-Type", "application/fhir+json;charset=utf-8")
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get fhir resource %q: %w", name, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("read: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
|
||||
}
|
||||
var jsonMap map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal response as json: %w", err)
|
||||
}
|
||||
return jsonMap, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 getfhirresource_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
getfhirresource "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirresource"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareGetFHIRResource(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-get-fhir-resource
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": getfhirresource.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-get-fhir-resource",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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 getfhirstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-get-fhir-store"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedFHIRStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{}
|
||||
if len(s.AllowedFHIRStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to get details for."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedFHIRStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
store, err := svc.Projects.Locations.Datasets.FhirStores.Get(storeName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get FHIR store %q: %w", storeName, err)
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 getfhirstore_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
getfhirstore "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstore"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareGetFHIRStore(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-get-fhir-store
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": getfhirstore.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-get-fhir-store",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// 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 getfhirstoremetrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-get-fhir-store-metrics"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedFHIRStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{}
|
||||
if len(s.AllowedFHIRStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to get metrics for."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedFHIRStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
store, err := svc.Projects.Locations.Datasets.FhirStores.GetFHIRStoreMetrics(storeName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get metrics for FHIR store %q: %w", storeName, err)
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 getfhirstoremetrics_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
getfhirstoremetrics "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstoremetrics"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareGetFHIRStoreMetrics(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-get-fhir-store-metrics
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": getfhirstoremetrics.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-get-fhir-store-metrics",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// 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 listdicomstores
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-list-dicom-stores"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedDICOMStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedDICOMStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
svc := t.Service
|
||||
var err error
|
||||
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", t.Project, t.Region, t.Dataset)
|
||||
stores, err := svc.Projects.Locations.Datasets.DicomStores.List(datasetName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err)
|
||||
}
|
||||
var filtered []*healthcare.DicomStore
|
||||
for _, store := range stores.DicomStores {
|
||||
if len(t.AllowedStores) == 0 {
|
||||
filtered = append(filtered, store)
|
||||
continue
|
||||
}
|
||||
if len(store.Name) == 0 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(store.Name, "/")
|
||||
if _, ok := t.AllowedStores[parts[len(parts)-1]]; ok {
|
||||
filtered = append(filtered, store)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 listdicomstores_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
listdicomstores "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarelistdicomstores"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareListDICOMStores(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-list-dicom-stores
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": listdicomstores.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-list-dicom-stores",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// 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 listfhirstores
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-list-fhir-stores"
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedFHIRStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedFHIRStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
svc := t.Service
|
||||
var err error
|
||||
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", t.Project, t.Region, t.Dataset)
|
||||
stores, err := svc.Projects.Locations.Datasets.FhirStores.List(datasetName).Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err)
|
||||
}
|
||||
var filtered []*healthcare.FhirStore
|
||||
for _, store := range stores.FhirStores {
|
||||
if len(t.AllowedStores) == 0 {
|
||||
filtered = append(filtered, store)
|
||||
continue
|
||||
}
|
||||
if len(store.Name) == 0 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(store.Name, "/")
|
||||
if _, ok := t.AllowedStores[parts[len(parts)-1]]; ok {
|
||||
filtered = append(filtered, store)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 listfhirstores_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
listfhirstores "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarelistfhirstores"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareListFHIRStores(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-list-fhir-stores
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": listfhirstores.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-list-fhir-stores",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// 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 retrieverendereddicominstance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-retrieve-rendered-dicom-instance"
|
||||
const (
|
||||
studyInstanceUIDKey = "StudyInstanceUID"
|
||||
seriesInstanceUIDKey = "SeriesInstanceUID"
|
||||
sopInstanceUIDKey = "SOPInstanceUID"
|
||||
frameNumberKey = "FrameNumber"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedDICOMStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{
|
||||
tools.NewStringParameter(studyInstanceUIDKey, "The UID of the DICOM study"),
|
||||
tools.NewStringParameter(seriesInstanceUIDKey, "The UID of the DICOM series"),
|
||||
tools.NewStringParameter(sopInstanceUIDKey, "The UID of the SOP instance."),
|
||||
tools.NewIntParameterWithDefault(frameNumberKey, 1, "The frame number to retrieve (1-based). Only applicable to multi-frame instances."),
|
||||
}
|
||||
if len(s.AllowedDICOMStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedDICOMStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
study, ok := params.AsMap()[studyInstanceUIDKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", studyInstanceUIDKey)
|
||||
}
|
||||
series, ok := params.AsMap()[seriesInstanceUIDKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", seriesInstanceUIDKey)
|
||||
}
|
||||
sop, ok := params.AsMap()[sopInstanceUIDKey].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", sopInstanceUIDKey)
|
||||
}
|
||||
frame, ok := params.AsMap()[frameNumberKey].(int)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected an integer", frameNumberKey)
|
||||
}
|
||||
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
dicomWebPath := fmt.Sprintf("studies/%s/series/%s/instances/%s/frames/%d/rendered", study, series, sop, frame)
|
||||
call := svc.Projects.Locations.Datasets.DicomStores.Studies.Series.Instances.Frames.RetrieveRendered(name, dicomWebPath)
|
||||
call.Header().Set("Accept", "image/jpeg")
|
||||
resp, err := call.Do()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to retrieve dicom instance rendered image: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("RetrieveRendered: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
|
||||
}
|
||||
base64String := base64.StdEncoding.EncodeToString(respBytes)
|
||||
return base64String, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 retrieverendereddicominstance_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
retrieverendereddicominstance "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcareretrieverendereddicominstance"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareRetrieveRenderedDICOMInstance(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-retrieve-rendered-dicom-instance
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": retrieverendereddicominstance.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-retrieve-rendered-dicom-instance",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
// 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 searchdicominstances
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-search-dicom-instances"
|
||||
const (
|
||||
studyInstanceUIDKey = "StudyInstanceUID"
|
||||
patientNameKey = "PatientName"
|
||||
patientIDKey = "PatientID"
|
||||
accessionNumberKey = "AccessionNumber"
|
||||
referringPhysicianNameKey = "ReferringPhysicianName"
|
||||
studyDateKey = "StudyDate"
|
||||
seriesInstanceUIDKey = "SeriesInstanceUID"
|
||||
modalityKey = "Modality"
|
||||
sopInstanceUIDKey = "SOPInstanceUID"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedDICOMStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{
|
||||
tools.NewStringParameterWithDefault(studyInstanceUIDKey, "", "The UID of the DICOM study"),
|
||||
tools.NewStringParameterWithDefault(patientNameKey, "", "The name of the patient"),
|
||||
tools.NewStringParameterWithDefault(patientIDKey, "", "The ID of the patient"),
|
||||
tools.NewStringParameterWithDefault(accessionNumberKey, "", "The accession number of the series"),
|
||||
tools.NewStringParameterWithDefault(referringPhysicianNameKey, "", "The name of the referring physician"),
|
||||
tools.NewStringParameterWithDefault(studyDateKey, "", "The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`"),
|
||||
tools.NewStringParameterWithDefault(seriesInstanceUIDKey, "", "The UID of the DICOM series"),
|
||||
tools.NewStringParameterWithDefault(modalityKey, "", "The modality of the series"),
|
||||
tools.NewStringParameterWithDefault(sopInstanceUIDKey, "", "The UID of the SOP instance."),
|
||||
tools.NewBooleanParameterWithDefault(common.EnablePatientNameFuzzyMatchingKey, false, `Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match`),
|
||||
tools.NewArrayParameterWithDefault(common.IncludeAttributesKey, []any{}, "List of attributeIDs, such as DICOM tag IDs or keywords. Set to [\"all\"] to return all available tags.", tools.NewStringParameter("attributeID", "The attributeID to include. Set to 'all' to return all available tags")),
|
||||
}
|
||||
if len(s.AllowedDICOMStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedDICOMStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
opts, err := common.ParseDICOMSearchParameters(params, []string{sopInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey, modalityKey})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paramsMap := params.AsMap()
|
||||
dicomWebPath := "instances"
|
||||
if studyInstanceUID, ok := paramsMap[studyInstanceUIDKey]; ok {
|
||||
id, ok := studyInstanceUID.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", studyInstanceUIDKey)
|
||||
}
|
||||
if id != "" {
|
||||
dicomWebPath = fmt.Sprintf("studies/%s/instances", id)
|
||||
}
|
||||
}
|
||||
if seriesInstanceUID, ok := paramsMap[seriesInstanceUIDKey]; ok {
|
||||
id, ok := seriesInstanceUID.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", seriesInstanceUIDKey)
|
||||
}
|
||||
if id != "" {
|
||||
if dicomWebPath != "instances" {
|
||||
dicomWebPath = fmt.Sprintf("%s/series/%s/instances", strings.TrimSuffix(dicomWebPath, "/instances"), id)
|
||||
} else {
|
||||
opts = append(opts, googleapi.QueryParameter(seriesInstanceUIDKey, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForInstances(name, dicomWebPath).Do(opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search dicom instances: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
|
||||
}
|
||||
if len(respBytes) == 0 {
|
||||
return []interface{}{}, nil
|
||||
}
|
||||
var result []interface{}
|
||||
if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal response as list: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 searchdicominstances_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
searchdicominstances "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicominstances"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareSearchDICOMInstances(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-search-dicom-instances
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": searchdicominstances.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-search-dicom-instances",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// 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 searchdicomseries
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-search-dicom-series"
|
||||
const (
|
||||
studyInstanceUIDKey = "StudyInstanceUID"
|
||||
patientNameKey = "PatientName"
|
||||
patientIDKey = "PatientID"
|
||||
accessionNumberKey = "AccessionNumber"
|
||||
referringPhysicianNameKey = "ReferringPhysicianName"
|
||||
studyDateKey = "StudyDate"
|
||||
seriesInstanceUIDKey = "SeriesInstanceUID"
|
||||
modalityKey = "Modality"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedDICOMStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{
|
||||
tools.NewStringParameterWithDefault(studyInstanceUIDKey, "", "The UID of the DICOM study"),
|
||||
tools.NewStringParameterWithDefault(patientNameKey, "", "The name of the patient"),
|
||||
tools.NewStringParameterWithDefault(patientIDKey, "", "The ID of the patient"),
|
||||
tools.NewStringParameterWithDefault(accessionNumberKey, "", "The accession number of the series"),
|
||||
tools.NewStringParameterWithDefault(referringPhysicianNameKey, "", "The name of the referring physician"),
|
||||
tools.NewStringParameterWithDefault(studyDateKey, "", "The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`"),
|
||||
tools.NewStringParameterWithDefault(seriesInstanceUIDKey, "", "The UID of the DICOM series"),
|
||||
tools.NewStringParameterWithDefault(modalityKey, "", "The modality of the series"),
|
||||
tools.NewBooleanParameterWithDefault(common.EnablePatientNameFuzzyMatchingKey, false, `Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match`),
|
||||
tools.NewArrayParameterWithDefault(common.IncludeAttributesKey, []any{}, "List of attributeIDs, such as DICOM tag IDs or keywords. Set to [\"all\"] to return all available tags.", tools.NewStringParameter("attributeID", "The attributeID to include. Set to 'all' to return all available tags")),
|
||||
}
|
||||
if len(s.AllowedDICOMStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedDICOMStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
opts, err := common.ParseDICOMSearchParameters(params, []string{seriesInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey, modalityKey})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paramsMap := params.AsMap()
|
||||
dicomWebPath := "series"
|
||||
if studyInstanceUID, ok := paramsMap[studyInstanceUIDKey]; ok {
|
||||
id, ok := studyInstanceUID.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", studyInstanceUIDKey)
|
||||
}
|
||||
if id != "" {
|
||||
dicomWebPath = fmt.Sprintf("studies/%s/series", id)
|
||||
}
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForSeries(name, dicomWebPath).Do(opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search dicom series: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
|
||||
}
|
||||
if len(respBytes) == 0 {
|
||||
return []interface{}{}, nil
|
||||
}
|
||||
var result []interface{}
|
||||
if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal response as list: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 searchdicomseries_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
searchdicomseries "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomseries"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareSearchDICOMSeries(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-search-dicom-series
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": searchdicomseries.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-search-dicom-series",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// 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 searchdicomstudies
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
|
||||
"google.golang.org/api/healthcare/v1"
|
||||
)
|
||||
|
||||
const kind string = "cloud-healthcare-search-dicom-studies"
|
||||
const (
|
||||
studyInstanceUIDKey = "StudyInstanceUID"
|
||||
patientNameKey = "PatientName"
|
||||
patientIDKey = "PatientID"
|
||||
accessionNumberKey = "AccessionNumber"
|
||||
referringPhysicianNameKey = "ReferringPhysicianName"
|
||||
studyDateKey = "StudyDate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if !tools.Register(kind, newConfig) {
|
||||
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Project() string
|
||||
Region() string
|
||||
DatasetID() string
|
||||
AllowedDICOMStores() map[string]struct{}
|
||||
Service() *healthcare.Service
|
||||
ServiceCreator() healthcareds.HealthcareServiceCreator
|
||||
UseClientAuthorization() bool
|
||||
}
|
||||
|
||||
// validate compatible sources are still compatible
|
||||
var _ compatibleSource = &healthcareds.Source{}
|
||||
|
||||
var compatibleSources = [...]string{healthcareds.SourceKind}
|
||||
|
||||
type Config struct {
|
||||
Name string `yaml:"name" validate:"required"`
|
||||
Kind string `yaml:"kind" validate:"required"`
|
||||
Source string `yaml:"source" validate:"required"`
|
||||
Description string `yaml:"description" validate:"required"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.ToolConfig = Config{}
|
||||
|
||||
func (cfg Config) ToolConfigKind() string {
|
||||
return kind
|
||||
}
|
||||
|
||||
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||
// verify source exists
|
||||
rawS, ok := srcs[cfg.Source]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
|
||||
}
|
||||
|
||||
// verify the source is compatible
|
||||
s, ok := rawS.(compatibleSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
|
||||
}
|
||||
|
||||
parameters := tools.Parameters{
|
||||
tools.NewStringParameterWithDefault(studyInstanceUIDKey, "", "The UID of the DICOM study"),
|
||||
tools.NewStringParameterWithDefault(patientNameKey, "", "The name of the patient"),
|
||||
tools.NewStringParameterWithDefault(patientIDKey, "", "The ID of the patient"),
|
||||
tools.NewStringParameterWithDefault(accessionNumberKey, "", "The accession number of the study"),
|
||||
tools.NewStringParameterWithDefault(referringPhysicianNameKey, "", "The name of the referring physician"),
|
||||
tools.NewStringParameterWithDefault(studyDateKey, "", "The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`"),
|
||||
tools.NewBooleanParameterWithDefault(common.EnablePatientNameFuzzyMatchingKey, false, `Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match`),
|
||||
tools.NewArrayParameterWithDefault(common.IncludeAttributesKey, []any{}, "List of attributeIDs, such as DICOM tag IDs or keywords. Set to [\"all\"] to return all available tags.", tools.NewStringParameter("attributeID", "The attributeID to include. Set to 'all' to return all available tags")),
|
||||
}
|
||||
if len(s.AllowedDICOMStores()) != 1 {
|
||||
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
|
||||
}
|
||||
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
|
||||
|
||||
// finish tool setup
|
||||
t := Tool{
|
||||
Name: cfg.Name,
|
||||
Kind: kind,
|
||||
Parameters: parameters,
|
||||
AuthRequired: cfg.AuthRequired,
|
||||
Project: s.Project(),
|
||||
Region: s.Region(),
|
||||
Dataset: s.DatasetID(),
|
||||
AllowedStores: s.AllowedDICOMStores(),
|
||||
UseClientOAuth: s.UseClientAuthorization(),
|
||||
ServiceCreator: s.ServiceCreator(),
|
||||
Service: s.Service(),
|
||||
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||
mcpManifest: mcpManifest,
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// validate interface
|
||||
var _ tools.Tool = Tool{}
|
||||
|
||||
type Tool struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
AuthRequired []string `yaml:"authRequired"`
|
||||
UseClientOAuth bool `yaml:"useClientOAuth"`
|
||||
Parameters tools.Parameters `yaml:"parameters"`
|
||||
|
||||
Project, Region, Dataset string
|
||||
AllowedStores map[string]struct{}
|
||||
Service *healthcare.Service
|
||||
ServiceCreator healthcareds.HealthcareServiceCreator
|
||||
manifest tools.Manifest
|
||||
mcpManifest tools.McpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
|
||||
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := t.Service
|
||||
// Initialize new service if using user OAuth token
|
||||
if t.UseClientOAuth {
|
||||
tokenStr, err := accessToken.ParseBearerToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing access token: %w", err)
|
||||
}
|
||||
svc, err = t.ServiceCreator(tokenStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
opts, err := common.ParseDICOMSearchParameters(params, []string{studyInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
|
||||
resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForStudies(name, "studies").Do(opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search dicom studies: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read response: %w", err)
|
||||
}
|
||||
if resp.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
|
||||
}
|
||||
if len(respBytes) == 0 {
|
||||
return []interface{}{}, nil
|
||||
}
|
||||
var result []interface{}
|
||||
if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal response as list: %w", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||
return tools.ParseParams(t.Parameters, data, claims)
|
||||
}
|
||||
|
||||
func (t Tool) Manifest() tools.Manifest {
|
||||
return t.manifest
|
||||
}
|
||||
|
||||
func (t Tool) McpManifest() tools.McpManifest {
|
||||
return t.mcpManifest
|
||||
}
|
||||
|
||||
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
|
||||
}
|
||||
|
||||
func (t Tool) RequiresClientAuthorization() bool {
|
||||
return t.UseClientOAuth
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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 searchdicomstudies_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/googleapis/genai-toolbox/internal/server"
|
||||
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||
searchdicomstudies "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomstudies"
|
||||
)
|
||||
|
||||
func TestParseFromYamlHealthcareSearchDICOMStudies(t *testing.T) {
|
||||
ctx, err := testutils.ContextWithNewLogger()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
tcs := []struct {
|
||||
desc string
|
||||
in string
|
||||
want server.ToolConfigs
|
||||
}{
|
||||
{
|
||||
desc: "basic example",
|
||||
in: `
|
||||
tools:
|
||||
example_tool:
|
||||
kind: cloud-healthcare-search-dicom-studies
|
||||
source: my-instance
|
||||
description: some description
|
||||
`,
|
||||
want: server.ToolConfigs{
|
||||
"example_tool": searchdicomstudies.Config{
|
||||
Name: "example_tool",
|
||||
Kind: "cloud-healthcare-search-dicom-studies",
|
||||
Source: "my-instance",
|
||||
Description: "some description",
|
||||
AuthRequired: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
got := struct {
|
||||
Tools server.ToolConfigs `yaml:"tools"`
|
||||
}{}
|
||||
// Parse contents
|
||||
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to unmarshal: %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||
t.Fatalf("incorrect parse: diff %v", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
91
internal/tools/cloudhealthcare/common/util.go
Normal file
91
internal/tools/cloudhealthcare/common/util.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// 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 common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||
"google.golang.org/api/googleapi"
|
||||
)
|
||||
|
||||
// StoreKey is the key used to identify FHIR/DICOM store IDs in tool parameters.
|
||||
const StoreKey = "storeID"
|
||||
|
||||
// EnablePatientNameFuzzyMatchingKey is the key used for DICOM search to enable
|
||||
// fuzzy matching.
|
||||
const EnablePatientNameFuzzyMatchingKey = "fuzzymatching"
|
||||
|
||||
// IncludeAttributesKey is the key used for DICOM search to include additional
|
||||
// tags in the response.
|
||||
const IncludeAttributesKey = "includefield"
|
||||
|
||||
// ValidateAndFetchStoreID validates the provided storeID against the allowedStores.
|
||||
// If only one store is allowed, it returns that storeID.
|
||||
// If multiple stores are allowed, it checks if the storeID parameter is in the allowed list.
|
||||
func ValidateAndFetchStoreID(params tools.ParamValues, allowedStores map[string]struct{}) (string, error) {
|
||||
if len(allowedStores) == 1 {
|
||||
for k := range allowedStores {
|
||||
return k, nil
|
||||
}
|
||||
}
|
||||
mapParams := params.AsMap()
|
||||
storeID, ok := mapParams[StoreKey].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("invalid or missing '%s' parameter; expected a string", StoreKey)
|
||||
}
|
||||
if len(allowedStores) > 0 {
|
||||
if _, ok := allowedStores[storeID]; !ok {
|
||||
return "", fmt.Errorf("store ID '%s' is not in the list of allowed stores", storeID)
|
||||
}
|
||||
}
|
||||
return storeID, nil
|
||||
}
|
||||
|
||||
// ParseDICOMSearchParameters extracts the search parameters for various DICOM
|
||||
// search methods.
|
||||
func ParseDICOMSearchParameters(params tools.ParamValues, paramKeys []string) ([]googleapi.CallOption, error) {
|
||||
var opts []googleapi.CallOption
|
||||
for k, v := range params.AsMap() {
|
||||
if k == IncludeAttributesKey {
|
||||
if _, ok := v.([]any); !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string array", k)
|
||||
}
|
||||
attributeIDsSlice, err := tools.ConvertAnySliceToTyped(v.([]any), "string")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't convert '%s' to array of strings: %s", k, err)
|
||||
}
|
||||
attributeIDs := attributeIDsSlice.([]string)
|
||||
if len(attributeIDs) != 0 {
|
||||
opts = append(opts, googleapi.QueryParameter(k, strings.Join(attributeIDs, ",")))
|
||||
}
|
||||
} else if k == EnablePatientNameFuzzyMatchingKey {
|
||||
if _, ok := v.(bool); !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a boolean", k)
|
||||
}
|
||||
opts = append(opts, googleapi.QueryParameter(k, fmt.Sprintf("%t", v.(bool))))
|
||||
} else if slices.Contains(paramKeys, k) {
|
||||
if _, ok := v.(string); !ok {
|
||||
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", k)
|
||||
}
|
||||
if v.(string) != "" {
|
||||
opts = append(opts, googleapi.QueryParameter(k, v.(string)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
2436
tests/cloudhealthcare/cloud_healthcare_integration_test.go
Normal file
2436
tests/cloudhealthcare/cloud_healthcare_integration_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user