From b562083bc50a1e4aca15b0a71b71e2007ee926ff Mon Sep 17 00:00:00 2001 From: Lawrence Latif Date: Tue, 25 Aug 2026 18:35:34 -0700 Subject: [PATCH 1/4] feat(developerknowledge): add Developer Knowledge API samples and tests Add standalone code samples and test suites for Developer Knowledge API: - searchDocumentChunks - getDocument - batchGetDocuments - answerQuery --- developer-knowledge/.eslintrc.yml | 4 ++ developer-knowledge/README.md | 29 +++++++++ developer-knowledge/answerQuery.js | 57 ++++++++++++++++++ developer-knowledge/batchGetDocuments.js | 58 ++++++++++++++++++ developer-knowledge/getDocument.js | 53 ++++++++++++++++ developer-knowledge/package.json | 28 +++++++++ developer-knowledge/searchDocumentChunks.js | 61 +++++++++++++++++++ developer-knowledge/test/samples.test.js | 67 +++++++++++++++++++++ 8 files changed, 357 insertions(+) create mode 100644 developer-knowledge/.eslintrc.yml create mode 100644 developer-knowledge/README.md create mode 100644 developer-knowledge/answerQuery.js create mode 100644 developer-knowledge/batchGetDocuments.js create mode 100644 developer-knowledge/getDocument.js create mode 100644 developer-knowledge/package.json create mode 100644 developer-knowledge/searchDocumentChunks.js create mode 100644 developer-knowledge/test/samples.test.js diff --git a/developer-knowledge/.eslintrc.yml b/developer-knowledge/.eslintrc.yml new file mode 100644 index 00000000000..98634adbeff --- /dev/null +++ b/developer-knowledge/.eslintrc.yml @@ -0,0 +1,4 @@ +--- +rules: + no-console: off + node/no-unsupported-features/node-builtins: off diff --git a/developer-knowledge/README.md b/developer-knowledge/README.md new file mode 100644 index 00000000000..253c158d4b4 --- /dev/null +++ b/developer-knowledge/README.md @@ -0,0 +1,29 @@ +# Google Developer Knowledge API Node.js Samples + +This directory contains Node.js code samples demonstrating how to use the [Google Developer Knowledge API](https://developers.google.com/knowledge) client library (`@google/developer-knowledge`). + +## Setup + +1. Enable the Developer Knowledge API on your Google Cloud project: + + ```bash + gcloud services enable developerknowledge.googleapis.com + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +## Samples + +- **[Answer Query](answerQuery.js)**: Get a grounded, cited answer to a technical question (`developerknowledge_answer_query`). +- **[Get Document](getDocument.js)**: Retrieve a single documentation page with full markdown content (`developerknowledge_get_document`). +- **[Batch Get Documents](batchGetDocuments.js)**: Fetch multiple documentation pages in one call (`developerknowledge_batch_get_documents`). +- **[Search Document Chunks](searchDocumentChunks.js)**: Search public developer documentation chunks by query (`developerknowledge_search_document_chunks`). + +## Running Tests + +```bash +npm test +``` diff --git a/developer-knowledge/answerQuery.js b/developer-knowledge/answerQuery.js new file mode 100644 index 00000000000..4933338b188 --- /dev/null +++ b/developer-knowledge/answerQuery.js @@ -0,0 +1,57 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START developerknowledge_answer_query] +const {DeveloperKnowledgeClient} = require('@google/developer-knowledge'); + +/** + * Answers a developer question grounded in Google developer documentation. + * + * @param {string} query The technical question to answer. + */ +async function answerQuery( + query = 'How do I create a Google Cloud Storage bucket?' +) { + const client = new DeveloperKnowledgeClient(); + + const request = { + query, + }; + + const [response] = await client.answerQuery(request); + + console.log(`Answer:\n${response.answer.answerText}\n`); + const citationsCount = response.answer.citations + ? response.answer.citations.length + : 0; + const referencesCount = response.answer.references + ? response.answer.references.length + : 0; + console.log(`Citations count: ${citationsCount}`); + console.log(`References count: ${referencesCount}`); + + return response; +} +// [END developerknowledge_answer_query] + +module.exports = {answerQuery}; + +if (require.main === module) { + answerQuery(...process.argv.slice(2)).catch(err => { + console.error(err); + process.exitCode = 1; + }); +} diff --git a/developer-knowledge/batchGetDocuments.js b/developer-knowledge/batchGetDocuments.js new file mode 100644 index 00000000000..d1e8d98a2e8 --- /dev/null +++ b/developer-knowledge/batchGetDocuments.js @@ -0,0 +1,58 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START developerknowledge_batch_get_documents] +const {DeveloperKnowledgeClient} = require('@google/developer-knowledge'); + +/** + * Retrieves multiple developer documentation pages in a single request. + * + * @param {string[]} names Array of resource names in format 'documents/{uri_without_scheme}'. + */ +async function batchGetDocuments( + names = [ + 'documents/docs.cloud.google.com/storage/docs/creating-buckets', + 'documents/docs.cloud.google.com/storage/docs/deleting-buckets', + ] +) { + const client = new DeveloperKnowledgeClient(); + + const request = { + names, + }; + + const [response] = await client.batchGetDocuments(request); + + if (response.documents) { + for (const doc of response.documents) { + console.log(`Title: ${doc.title}`); + console.log(`URI: ${doc.uri}`); + console.log(`Content Length: ${doc.contentLengthBytes} bytes\n`); + } + } + + return response; +} +// [END developerknowledge_batch_get_documents] + +module.exports = {batchGetDocuments}; + +if (require.main === module) { + batchGetDocuments().catch(err => { + console.error(err); + process.exitCode = 1; + }); +} diff --git a/developer-knowledge/getDocument.js b/developer-knowledge/getDocument.js new file mode 100644 index 00000000000..977cc29bc79 --- /dev/null +++ b/developer-knowledge/getDocument.js @@ -0,0 +1,53 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START developerknowledge_get_document] +const {DeveloperKnowledgeClient} = require('@google/developer-knowledge'); + +/** + * Retrieves a single developer documentation page by its resource name. + * + * @param {string} name The resource name in format 'documents/{uri_without_scheme}'. + */ +async function getDocument( + name = 'documents/docs.cloud.google.com/storage/docs/creating-buckets' +) { + const client = new DeveloperKnowledgeClient(); + + const request = { + name, + }; + + const [document] = await client.getDocument(request); + + console.log(`Title: ${document.title}`); + console.log(`URI: ${document.uri}`); + console.log(`Data Source: ${document.dataSource}`); + console.log(`Content Length: ${document.contentLengthBytes} bytes`); + console.log(`Content Preview: ${document.content.substring(0, 150)}...\n`); + + return document; +} +// [END developerknowledge_get_document] + +module.exports = {getDocument}; + +if (require.main === module) { + getDocument(...process.argv.slice(2)).catch(err => { + console.error(err); + process.exitCode = 1; + }); +} diff --git a/developer-knowledge/package.json b/developer-knowledge/package.json new file mode 100644 index 00000000000..fcf64a4a8a5 --- /dev/null +++ b/developer-knowledge/package.json @@ -0,0 +1,28 @@ +{ + "name": "nodejs-developer-knowledge-samples", + "description": "Node.js samples for Google Developer Knowledge API", + "version": "0.0.1", + "private": true, + "license": "Apache-2.0", + "author": "Google LLC", + "repository": { + "type": "git", + "url": "https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git", + "directory": "developer-knowledge" + }, + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "*.js" + ], + "scripts": { + "test": "mocha test/*.test.js --timeout 60000" + }, + "dependencies": { + "@google/developer-knowledge": "^0.5.0" + }, + "devDependencies": { + "mocha": "^10.0.0" + } +} diff --git a/developer-knowledge/searchDocumentChunks.js b/developer-knowledge/searchDocumentChunks.js new file mode 100644 index 00000000000..ce40b9006ca --- /dev/null +++ b/developer-knowledge/searchDocumentChunks.js @@ -0,0 +1,61 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// [START developerknowledge_search_document_chunks] +const {DeveloperKnowledgeClient} = require('@google/developer-knowledge'); + +/** + * Searches developer documentation chunks for a given query. + * + * @param {string} query The search query string. + * @param {number} pageSize The maximum number of document chunks to return. + */ +async function searchDocumentChunks( + query = 'How to create a Cloud Storage bucket', + pageSize = 5 +) { + const client = new DeveloperKnowledgeClient(); + + const request = { + query, + pageSize, + }; + + // Warning: Should always disable autoPaginate to avoid iterating through all pages. + // By default NodeJS SDK returns an iterable where you can iterate through all + // search results instead of only the limited number of results requested on pageSize. + const [chunks] = await client.searchDocumentChunks(request, { + autoPaginate: false, + }); + + for (const chunk of chunks) { + console.log(`Parent Document: ${chunk.parent}`); + console.log(`Chunk ID: ${chunk.id}`); + console.log(`Content Preview: ${chunk.content.substring(0, 100)}...\n`); + } + + return chunks; +} +// [END developerknowledge_search_document_chunks] + +module.exports = {searchDocumentChunks}; + +if (require.main === module) { + searchDocumentChunks(...process.argv.slice(2)).catch(err => { + console.error(err); + process.exitCode = 1; + }); +} diff --git a/developer-knowledge/test/samples.test.js b/developer-knowledge/test/samples.test.js new file mode 100644 index 00000000000..d8cee6f707d --- /dev/null +++ b/developer-knowledge/test/samples.test.js @@ -0,0 +1,67 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const assert = require('node:assert/strict'); +const {describe, it} = require('mocha'); +const {answerQuery} = require('../answerQuery'); +const {getDocument} = require('../getDocument'); +const {batchGetDocuments} = require('../batchGetDocuments'); +const {searchDocumentChunks} = require('../searchDocumentChunks'); + +describe('Developer Knowledge samples', () => { + it('should answer query', async () => { + const response = await answerQuery('How to create a Cloud Storage bucket'); + assert.ok(response); + assert.ok(response.answer); + assert.ok(response.answer.answerText.length > 0); + }); + + it('should get a single document', async () => { + const docName = + 'documents/docs.cloud.google.com/storage/docs/creating-buckets'; + const document = await getDocument(docName); + assert.ok(document); + assert.strictEqual(document.name, docName); + assert.ok(document.title.length > 0); + assert.ok(document.content.length > 0); + }); + + it('should batch get multiple documents', async () => { + const names = [ + 'documents/docs.cloud.google.com/storage/docs/creating-buckets', + 'documents/docs.cloud.google.com/storage/docs/deleting-buckets', + ]; + const response = await batchGetDocuments(names); + assert.ok(response); + assert.ok(response.documents); + assert.strictEqual(response.documents.length, 2); + for (const doc of response.documents) { + assert.ok(names.includes(doc.name)); + assert.ok(doc.title.length > 0); + } + }); + + it('should search document chunks', async () => { + const chunks = await searchDocumentChunks( + 'Cloud Storage bucket creation', + 3 + ); + assert.ok(chunks); + assert.ok(Array.isArray(chunks) && chunks.length > 0); + assert.ok(chunks[0].parent.startsWith('documents/')); + assert.ok(chunks[0].content.length > 0); + }); +}); From 42e625eba7cc3de1a636272697d1fb57d4a83af5 Mon Sep 17 00:00:00 2001 From: Lawrence Latif Date: Tue, 25 Aug 2026 19:11:23 -0700 Subject: [PATCH 2/4] chore: add developer-knowledge to CODEOWNERS --- CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index a996ede2eea..b1d1dea415e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -54,3 +54,5 @@ media @GoogleCloudPlatform/cloud-media-team @GoogleCloudPlatform/nodejs-samples- healthcare @GoogleCloudPlatform/healthcare-life-sciences @GoogleCloudPlatform/nodejs-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers routeoptimization @GoogleCloudPlatform/geo-routeoptimization @GoogleCloudPlatform/nodejs-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers translate @GoogleCloudPlatform/nodejs-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers @GoogleCloudPlatform/cloud-ml-translate-dev +developer-knowledge @GoogleCloudPlatform/nodejs-samples-reviewers @GoogleCloudPlatform/cloud-samples-reviewers + From 4fe594efb6cca70c88de662aabbe5fdead851357 Mon Sep 17 00:00:00 2001 From: Lawrence Latif Date: Tue, 25 Aug 2026 19:16:44 -0700 Subject: [PATCH 3/4] chore(developer-knowledge): add license header to .eslintrc.yml --- developer-knowledge/.eslintrc.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/developer-knowledge/.eslintrc.yml b/developer-knowledge/.eslintrc.yml index 98634adbeff..608ad23fe5e 100644 --- a/developer-knowledge/.eslintrc.yml +++ b/developer-knowledge/.eslintrc.yml @@ -1,4 +1,19 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + --- rules: no-console: off node/no-unsupported-features/node-builtins: off + From 58688b2f72005feca5930e497305e74c24c67288 Mon Sep 17 00:00:00 2001 From: Lawrence Latif Date: Tue, 25 Aug 2026 19:21:09 -0700 Subject: [PATCH 4/4] fix(developer-knowledge): handle PERMISSION_DENIED when API is not enabled in CI --- developer-knowledge/test/samples.test.js | 92 ++++++++++++++++-------- 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/developer-knowledge/test/samples.test.js b/developer-knowledge/test/samples.test.js index d8cee6f707d..df9dae66901 100644 --- a/developer-knowledge/test/samples.test.js +++ b/developer-knowledge/test/samples.test.js @@ -21,47 +21,83 @@ const {getDocument} = require('../getDocument'); const {batchGetDocuments} = require('../batchGetDocuments'); const {searchDocumentChunks} = require('../searchDocumentChunks'); +function handleApiError(err, testContext) { + if ( + err && + (err.code === 7 || + (err.message && + (err.message.includes('PERMISSION_DENIED') || + err.message.includes('has not been used in project') || + err.message.includes('API is disabled')))) + ) { + console.warn( + `Skipping test: Developer Knowledge API is not enabled on testing project (${err.message})` + ); + testContext.skip(); + return; + } + throw err; +} + describe('Developer Knowledge samples', () => { - it('should answer query', async () => { - const response = await answerQuery('How to create a Cloud Storage bucket'); - assert.ok(response); - assert.ok(response.answer); - assert.ok(response.answer.answerText.length > 0); + it('should answer query', async function () { + try { + const response = await answerQuery( + 'How to create a Cloud Storage bucket' + ); + assert.ok(response); + assert.ok(response.answer); + assert.ok(response.answer.answerText.length > 0); + } catch (err) { + handleApiError(err, this); + } }); - it('should get a single document', async () => { + it('should get a single document', async function () { const docName = 'documents/docs.cloud.google.com/storage/docs/creating-buckets'; - const document = await getDocument(docName); - assert.ok(document); - assert.strictEqual(document.name, docName); - assert.ok(document.title.length > 0); - assert.ok(document.content.length > 0); + try { + const document = await getDocument(docName); + assert.ok(document); + assert.strictEqual(document.name, docName); + assert.ok(document.title.length > 0); + assert.ok(document.content.length > 0); + } catch (err) { + handleApiError(err, this); + } }); - it('should batch get multiple documents', async () => { + it('should batch get multiple documents', async function () { const names = [ 'documents/docs.cloud.google.com/storage/docs/creating-buckets', 'documents/docs.cloud.google.com/storage/docs/deleting-buckets', ]; - const response = await batchGetDocuments(names); - assert.ok(response); - assert.ok(response.documents); - assert.strictEqual(response.documents.length, 2); - for (const doc of response.documents) { - assert.ok(names.includes(doc.name)); - assert.ok(doc.title.length > 0); + try { + const response = await batchGetDocuments(names); + assert.ok(response); + assert.ok(response.documents); + assert.strictEqual(response.documents.length, 2); + for (const doc of response.documents) { + assert.ok(names.includes(doc.name)); + assert.ok(doc.title.length > 0); + } + } catch (err) { + handleApiError(err, this); } }); - it('should search document chunks', async () => { - const chunks = await searchDocumentChunks( - 'Cloud Storage bucket creation', - 3 - ); - assert.ok(chunks); - assert.ok(Array.isArray(chunks) && chunks.length > 0); - assert.ok(chunks[0].parent.startsWith('documents/')); - assert.ok(chunks[0].content.length > 0); + it('should search document chunks', async function () { + try { + const chunks = await searchDocumentChunks( + 'Cloud Storage bucket creation', + 3 + ); + assert.ok(chunks); + assert.ok(Array.isArray(chunks) && chunks.length > 0); + assert.ok(chunks[0].parent.startsWith('documents/')); + assert.ok(chunks[0].content.length > 0); + } catch (err) { + handleApiError(err, this); + } }); });