From 195feba5c4c67217ff5de68b81ecf5241c3a4552 Mon Sep 17 00:00:00 2001 From: Dan Debrunner Date: Wed, 12 Nov 2025 13:39:34 -0500 Subject: [PATCH 1/4] watsonx.ai generation snippet --- ai/watsonxai-generation/README.md | 45 ++++++++++ ai/watsonxai-generation/config.yaml | 10 +++ ai/watsonxai-generation/ibm-iam.graphql | 33 +++++++ ai/watsonxai-generation/index.graphql | 13 +++ ai/watsonxai-generation/op.graphql | 13 +++ ai/watsonxai-generation/stepzen.config.json | 3 + ai/watsonxai-generation/tests/Test.js | 11 +++ ai/watsonxai-generation/watsonx-ai.graphql | 98 +++++++++++++++++++++ 8 files changed, 226 insertions(+) create mode 100644 ai/watsonxai-generation/README.md create mode 100644 ai/watsonxai-generation/config.yaml create mode 100644 ai/watsonxai-generation/ibm-iam.graphql create mode 100644 ai/watsonxai-generation/index.graphql create mode 100644 ai/watsonxai-generation/op.graphql create mode 100644 ai/watsonxai-generation/stepzen.config.json create mode 100644 ai/watsonxai-generation/tests/Test.js create mode 100644 ai/watsonxai-generation/watsonx-ai.graphql diff --git a/ai/watsonxai-generation/README.md b/ai/watsonxai-generation/README.md new file mode 100644 index 0000000..a4594c5 --- /dev/null +++ b/ai/watsonxai-generation/README.md @@ -0,0 +1,45 @@ +# Watsonx.AI generation. + +## Overview + +Creates a field `Query.wxai_generation` that invokes a Watsonx.AI generation endpoint to infer the next text from an input. + +This provides a simple example of calling an LLM from a GraphQL endpoint. + +An IBM Cloud IAM token is used for authorization, taken from the [ibm-iam snippet](../../rest/ibm-iam/). + +## Try it out + +Deploy the schema with this values set in `.env` for your Watsonx.AI project. + +STEPZEN_IBM_IAM_APIKEY= +STEPZEN_WATSONXAI_URL= +STEPZEN_WATSONXAI_PROJECTID= + +Then you can use `stepzen request` and the provided operation to ask simple questions: + +``` +stepzen request --file op.graphql --var question="when was hadrian's wall built" +``` + +recieving a GraphQL response like: + +``` +{ + "data": { + "wxai_generation": { + "created_at": "2025-11-12T18:29:23.65Z", + "model_id": "ibm/granite-3-8b-instruct", + "model_version": "1.1.0", + "results": [ + { + "generated_text": "\n\nHadrian's Wall was built between 122 and 128 AD", + "generated_token_count": 20, + "input_token_count": 7, + "stop_reason": "max_tokens" + } + ] + } + } +} +``` diff --git a/ai/watsonxai-generation/config.yaml b/ai/watsonxai-generation/config.yaml new file mode 100644 index 0000000..5675662 --- /dev/null +++ b/ai/watsonxai-generation/config.yaml @@ -0,0 +1,10 @@ +configurationset: +- configuration: + name: ibm-iam + endpoint: https://iam.cloud.ibm.com/identity/token + apikey: STEPZEN_IBM_IAM_APIKEY +- configuration: + name: watsonx-service + version: '2024-06-28' + url: STEPZEN_WATSONXAI_URL + project_id: STEPZEN_WATSONXAI_PROJECTID diff --git a/ai/watsonxai-generation/ibm-iam.graphql b/ai/watsonxai-generation/ibm-iam.graphql new file mode 100644 index 0000000..3de1d0c --- /dev/null +++ b/ai/watsonxai-generation/ibm-iam.graphql @@ -0,0 +1,33 @@ +extend type Query { + """ + Obtain an IBM Cloud bearer token. + + Uses the [IBM Cloud API Key](https://cloud.ibm.com/docs/account?topic=account-userapikey&interface=ui#userapikey) + or [service ID's API Key](https://cloud.ibm.com/docs/account?topic=account-serviceidapikeys&interface=ui) + to [generate an IAM Token](https://cloud.ibm.com/docs/account?topic=account-iamtoken_from_apikey#iamtoken_from_apikey) + """ + ibm_iam_token: Secret + @rest( + endpoint: "$endpoint" + method: POST + contenttype: "x-www-form-urlencoded" + postbody: """ + grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey={{ .Get "apikey"}} + """ + ecmascript: """ + function transformREST(body) { + switch (status) { + case 200: + return body + case 401: + case 400: // returned for apikey not found + throw new Error('unauthorized'); + default: + throw new Error('unknown error'); + } + } + """ + setters: { path: "access_token" } + configuration: "ibm-iam" + ) +} diff --git a/ai/watsonxai-generation/index.graphql b/ai/watsonxai-generation/index.graphql new file mode 100644 index 0000000..45238ad --- /dev/null +++ b/ai/watsonxai-generation/index.graphql @@ -0,0 +1,13 @@ +""" +Snippet demonstrating calling Watsonx.AI's generation endpoint. +""" +schema + @sdl( + files: ["watsonx-ai.graphql", "ibm-iam.graphql"] + # Only expose wxai_ fields + visibility: { expose: true, types: "Query", fields: "wxai_.*" } + # ensure the operation remains valid + executables: { document: "op.graphql" } + ) { + query: Query +} diff --git a/ai/watsonxai-generation/op.graphql b/ai/watsonxai-generation/op.graphql new file mode 100644 index 0000000..9584545 --- /dev/null +++ b/ai/watsonxai-generation/op.graphql @@ -0,0 +1,13 @@ +query AskWatsonxAI($question: String!) { + wxai_generation(input: $question) { + created_at + model_id + model_version + results { + generated_text + generated_token_count + input_token_count + stop_reason + } + } +} diff --git a/ai/watsonxai-generation/stepzen.config.json b/ai/watsonxai-generation/stepzen.config.json new file mode 100644 index 0000000..af1c0ea --- /dev/null +++ b/ai/watsonxai-generation/stepzen.config.json @@ -0,0 +1,3 @@ +{ + "endpoint": "api/miscellaneous" +} diff --git a/ai/watsonxai-generation/tests/Test.js b/ai/watsonxai-generation/tests/Test.js new file mode 100644 index 0000000..1596c11 --- /dev/null +++ b/ai/watsonxai-generation/tests/Test.js @@ -0,0 +1,11 @@ +const { + deployAndRun, + getTestDescription, +} = require("../../../tests/gqltest.js"); + +testDescription = getTestDescription("snippets", __dirname); + +describe(testDescription, function () { + const tests = []; + return deployAndRun(__dirname, tests); +}); diff --git a/ai/watsonxai-generation/watsonx-ai.graphql b/ai/watsonxai-generation/watsonx-ai.graphql new file mode 100644 index 0000000..870e2fe --- /dev/null +++ b/ai/watsonxai-generation/watsonx-ai.graphql @@ -0,0 +1,98 @@ +extend type Query { + """ + Infers the next tokens for a given deployed model from the `input` text. + """ + wxai_generation( + input: String + model_id: String! = "ibm/granite-3-8b-instruct" + parameters: WXAI_GenerationParameters + ): WXAI_GenerationResponse + @sequence( + steps: [ + { query: "ibm_iam_token" } + { + query: "_wxai_generation" + arguments: [ + { name: "token", field: "ยง0" } + { name: "input", argument: "input" } + { name: "model_id", argument: "model_id" } + { name: "parameters", argument: "parameters" } + ] + } + ] + ) + + """ + Calls the generation endpoint. + """ + _wxai_generation( + token: Secret! + input: String + model_id: String + parameters: WXAI_GenerationParameters + ): WXAI_GenerationResponse + @rest( + method: POST + endpoint: "$url;" + path: "/ml/v1/text/generation?version=$version" + headers: [{ name: "authorization", value: "Bearer $token" }] + ecmascript: """ + function bodyPOST(s) { + let body = JSON.parse(s) + body.project_id = get("project_id") + return JSON.stringify(body) + } + """ + configuration: "watsonx-service" + ) +} + +""" +The parameters for the model of a generative AI request. See https://cloud.ibm.com/apidocs/watsonx-ai#text-generation for more details. +""" +input WXAI_GenerationParameters { + """ + The temperature specifies the amount of variation in the generation process when using sampling mode. + """ + temperature: Float = 0 + """ + The strategy the model uses to choose the tokens in the generated output. + """ + decoding_method: String + """ + The minimum number of new tokens to be generated. + """ + min_new_tokens: Int + """ + The maximum number of new tokens to be generated. + """ + max_new_tokens: Int = 20 + """ + The repetition penalty lowers the probability scores of tokens that have already been generated or belong to the context. + """ + repetition_penalty: Float + """ + A string of one or more characters which will stop the text generation if/when they appear in the generated output. + """ + stop_sequences: [String] +} + +""" +Response of IBM Generative AI watsonx.ai `generation` endpoint +""" +type WXAI_GenerationResponse { + created_at: DateTime + model_id: String + model_version: String + results: [WXAI_GenerationResult] +} + +""" +A result in the response of IBM Generative AI watsonx.ai `generation` endpoint +""" +type WXAI_GenerationResult { + generated_text: String + generated_token_count: Int + input_token_count: Int + stop_reason: String +} From d59bffcfa378fe050b788be6c2b7a36abd16ec01 Mon Sep 17 00:00:00 2001 From: Dan Debrunner Date: Wed, 12 Nov 2025 13:49:03 -0500 Subject: [PATCH 2/4] chore; cleanup --- ai/README.md | 1 + ai/watsonxai-generation/README.md | 16 +++++++++++++--- ai/watsonxai-generation/watsonx-ai.graphql | 4 ++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ai/README.md b/ai/README.md index f74c53f..e72c9bd 100644 --- a/ai/README.md +++ b/ai/README.md @@ -13,3 +13,4 @@ Articles: Snippets: - [Prescribed tools](../executable/prescribed/) - Demonstrates creation of MCP tools from persisted GraphQL operations. +- [watsonx.AI generation](watsonxai-generation/) - Demonstrates invoking an LLM through watsonx.AI diff --git a/ai/watsonxai-generation/README.md b/ai/watsonxai-generation/README.md index a4594c5..aae28f0 100644 --- a/ai/watsonxai-generation/README.md +++ b/ai/watsonxai-generation/README.md @@ -1,8 +1,8 @@ -# Watsonx.AI generation. +# watsonx.ai generation. ## Overview -Creates a field `Query.wxai_generation` that invokes a Watsonx.AI generation endpoint to infer the next text from an input. +Creates a field `Query.wxai_generation` that invokes a watsonx.ai generation endpoint to infer the next text from an input. This provides a simple example of calling an LLM from a GraphQL endpoint. @@ -10,11 +10,21 @@ An IBM Cloud IAM token is used for authorization, taken from the [ibm-iam snippe ## Try it out -Deploy the schema with this values set in `.env` for your Watsonx.AI project. +### Deploying +Deploy the schema with this values set in `.env` for your watsonx.ai project. + +``` STEPZEN_IBM_IAM_APIKEY= STEPZEN_WATSONXAI_URL= STEPZEN_WATSONXAI_PROJECTID= +``` + +These can be obtained from the watsonx dashboard: + +image + +### Sample request Then you can use `stepzen request` and the provided operation to ask simple questions: diff --git a/ai/watsonxai-generation/watsonx-ai.graphql b/ai/watsonxai-generation/watsonx-ai.graphql index 870e2fe..c9a760d 100644 --- a/ai/watsonxai-generation/watsonx-ai.graphql +++ b/ai/watsonxai-generation/watsonx-ai.graphql @@ -78,7 +78,7 @@ input WXAI_GenerationParameters { } """ -Response of IBM Generative AI watsonx.ai `generation` endpoint +Response of IBM watsonx.ai `generation` endpoint """ type WXAI_GenerationResponse { created_at: DateTime @@ -88,7 +88,7 @@ type WXAI_GenerationResponse { } """ -A result in the response of IBM Generative AI watsonx.ai `generation` endpoint +A result in the response of IBM watsonx.ai `generation` endpoint """ type WXAI_GenerationResult { generated_text: String From 7ee42b42f7b1dd20307e806695970257c799810c Mon Sep 17 00:00:00 2001 From: Dan Debrunner Date: Wed, 12 Nov 2025 13:54:06 -0500 Subject: [PATCH 3/4] fix headers --- ai/README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ai/README.md b/ai/README.md index e72c9bd..dc45c16 100644 --- a/ai/README.md +++ b/ai/README.md @@ -1,6 +1,6 @@ # AI related snippets -# MCP +## MCP Every API Connect for GraphQL deployed schema has an MCP `/mcp` endpoint in addition to its `/graphql` endpoint. @@ -13,4 +13,7 @@ Articles: Snippets: - [Prescribed tools](../executable/prescribed/) - Demonstrates creation of MCP tools from persisted GraphQL operations. -- [watsonx.AI generation](watsonxai-generation/) - Demonstrates invoking an LLM through watsonx.AI + +## AI snippets + +- [watsonx.AI generation](watsonxai-generation/) - Demonstrates invoking an LLM through watsonx.AI within a GraphQL endpoint. From 4146af0d82a17e5dd317029ff71cc6e2f0de4ed8 Mon Sep 17 00:00:00 2001 From: Dan Debrunner Date: Wed, 12 Nov 2025 13:54:53 -0500 Subject: [PATCH 4/4] fix headers --- ai/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ai/README.md b/ai/README.md index dc45c16..dd273a1 100644 --- a/ai/README.md +++ b/ai/README.md @@ -5,12 +5,12 @@ Every API Connect for GraphQL deployed schema has an MCP `/mcp` endpoint in addition to its `/graphql` endpoint. -Articles: +### Articles - https://developer.ibm.com/articles/awb-simplifying-llm-integration-mcp-api-connect-graphql/ - https://community.ibm.com/community/user/blogs/timil-titus/2025/10/12/mcp-and-ibm-api-connect-for-graphql -Snippets: +### Snippets - [Prescribed tools](../executable/prescribed/) - Demonstrates creation of MCP tools from persisted GraphQL operations.