Skip to content

Commit 59b1de9

Browse files
daniel-lxsPrasangAPrajapati
authored andcommitted
refactor: reduce IBM watsonx provider bloat and improve code quality
- Reduce UI component from 470 to 313 lines (33% reduction) - Remove redundant manual refresh button (follows LMStudio/Ollama pattern) - Extract validation helper function to reduce duplication - Consolidate duplicate credential input fields - Fix region naming to use actual codes instead of pretty names - Simplify handler variable declarations and error handling - Remove redundant validation checks in fetcher - Add documented constant for non-inference model filtering - Overall 19% reduction across all watsonx files (913 -> 735 lines)
1 parent f34d191 commit 59b1de9

File tree

4 files changed

+166
-341
lines changed

4 files changed

+166
-341
lines changed

packages/types/src/providers/ibm-watsonx.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ export const REGION_TO_URL: Record<string, string> = {
1010
"ap-south-1": "https://ap-south-1.aws.wxai.ibm.com",
1111
}
1212

13+
/**
14+
* Models that are not suitable for general text inference tasks.
15+
* These are typically guard/safety models used for content moderation.
16+
*/
17+
export const WATSONX_NON_INFERENCE_MODELS = [
18+
"meta-llama/llama-guard-3-11b-vision",
19+
"ibm/granite-guardian-3-8b",
20+
"ibm/granite-guardian-3-2b",
21+
] as const
22+
1323
export type WatsonxAIModelId = keyof typeof watsonxModels
1424
export const watsonxDefaultModelId = "ibm/granite-3-3-8b-instruct"
1525

src/api/providers/fetchers/ibm-watsonx.ts

Lines changed: 26 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ModelInfo, REGION_TO_URL } from "@roo-code/types"
1+
import { ModelInfo, REGION_TO_URL, WATSONX_NON_INFERENCE_MODELS } from "@roo-code/types"
22
import { IamAuthenticator, CloudPakForDataAuthenticator, UserOptions } from "ibm-cloud-sdk-core"
33
import { WatsonXAI } from "@ibm-cloud/watsonx-ai"
44
import WatsonxAiMlVml_v1 from "@ibm-cloud/watsonx-ai/dist/watsonx-ai-ml/vml_v1.js"
@@ -67,20 +67,18 @@ export async function getWatsonxModels(
6767
throw new Error("Password is required for IBM Cloud Pak for Data")
6868
}
6969
options.serviceUrl = baseUrl
70-
if (username) {
71-
if (password) {
72-
options.authenticator = new CloudPakForDataAuthenticator({
73-
url: `${baseUrl}/icp4d-api`,
74-
username: username,
75-
password: password,
76-
})
77-
} else if (apiKey) {
78-
options.authenticator = new CloudPakForDataAuthenticator({
79-
url: `${baseUrl}/icp4d-api`,
80-
username: username,
81-
apikey: apiKey,
82-
})
83-
}
70+
if (password) {
71+
options.authenticator = new CloudPakForDataAuthenticator({
72+
url: `${baseUrl}/icp4d-api`,
73+
username,
74+
password,
75+
})
76+
} else {
77+
options.authenticator = new CloudPakForDataAuthenticator({
78+
url: `${baseUrl}/icp4d-api`,
79+
username,
80+
apikey: apiKey,
81+
})
8482
}
8583
}
8684

@@ -96,39 +94,21 @@ export async function getWatsonxModels(
9694
if (Array.isArray(modelsList) && modelsList.length > 0) {
9795
for (const model of modelsList) {
9896
const modelId = model.model_id
99-
let contextWindow = 131072
100-
if (model.model_limits && model.model_limits.max_sequence_length) {
101-
contextWindow = model.model_limits.max_sequence_length
102-
}
103-
let maxTokens = Math.floor(contextWindow / 16)
104-
if (
105-
model.model_limits &&
106-
model.training_parameters &&
107-
model.training_parameters.max_output_tokens &&
108-
model.training_parameters.max_output_tokens.max
109-
) {
110-
maxTokens = model.training_parameters.max_output_tokens.max
111-
}
11297

113-
let description = ""
114-
if (model.long_description) {
115-
description = model.long_description
116-
} else if (model.short_description) {
117-
description = model.short_description
98+
if (WATSONX_NON_INFERENCE_MODELS.includes(modelId as any)) {
99+
continue
118100
}
119-
if (
120-
!(
121-
modelId === "meta-llama/llama-guard-3-11b-vision" ||
122-
modelId === "ibm/granite-guardian-3-8b" ||
123-
modelId === "ibm/granite-guardian-3-2b"
124-
)
125-
) {
126-
knownModels[modelId] = {
127-
contextWindow,
128-
maxTokens,
129-
supportsPromptCache: false,
130-
description,
131-
}
101+
102+
const contextWindow = model.model_limits?.max_sequence_length || 131072
103+
const maxTokens =
104+
model.training_parameters?.max_output_tokens?.max || Math.floor(contextWindow / 16)
105+
const description = model.long_description || model.short_description || ""
106+
107+
knownModels[modelId] = {
108+
contextWindow,
109+
maxTokens,
110+
supportsPromptCache: false,
111+
description,
132112
}
133113
}
134114
}

src/api/providers/ibm-watsonx.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,32 +148,23 @@ export class WatsonxAIHandler extends BaseProvider implements SingleCompletionHa
148148
const watsonxMessages = [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)]
149149

150150
const params = this.createTextChatParams(this.projectId!, modelId, watsonxMessages)
151-
let responseText = ""
152151

153-
// Call the IBM watsonx API using textChat (non-streaming); can be changed to streaming..
154152
const response = await this.service.textChat(params)
155153

156154
if (!response?.result?.choices?.[0]?.message?.content) {
157155
throw new Error("Invalid or empty response from IBM watsonx API")
158156
}
159157

160-
responseText = response.result.choices[0].message.content
158+
const responseText = response.result.choices[0].message.content
161159

162160
yield {
163161
type: "text",
164162
text: responseText,
165163
}
166-
let usageInfo: WatsonXAI.TextChatUsage
167-
usageInfo = response.result.usage || {}
168164

169-
let outputTokens = 0
170-
if (usageInfo.completion_tokens) {
171-
outputTokens = usageInfo.completion_tokens
172-
} else {
173-
console.error("[IBM watsonx] Failed to count output tokens:")
174-
}
175-
176-
const inputTokens = usageInfo?.prompt_tokens || 0
165+
const usageInfo = response.result.usage || {}
166+
const inputTokens = usageInfo.prompt_tokens || 0
167+
const outputTokens = usageInfo.completion_tokens || 0
177168
const modelInfo = this.getModel().info
178169
const totalCost = calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens)
179170

@@ -184,17 +175,18 @@ export class WatsonxAIHandler extends BaseProvider implements SingleCompletionHa
184175
totalCost: totalCost,
185176
}
186177
} catch (error) {
187-
// Extract error message and type from the error object
188178
const errorMessage = error?.message || String(error)
189179
const errorType = error?.type || undefined
180+
190181
let detailedMessage = errorMessage
191182
if (errorMessage.includes("401") || errorMessage.includes("Unauthorized")) {
192-
detailedMessage = `Authentication failed: ${errorMessage}. Please check your API key and credentials.`
183+
detailedMessage = `Authentication failed. Please check your API key and credentials.`
193184
} else if (errorMessage.includes("404")) {
194-
detailedMessage = `Model or endpoint not found: ${errorMessage}. Please verify the model ID and base URL.`
185+
detailedMessage = `Model or endpoint not found. Please verify the model ID and base URL.`
195186
} else if (errorMessage.includes("timeout") || errorMessage.includes("ECONNREFUSED")) {
196-
detailedMessage = `Connection failed: ${errorMessage}. Please check your network connection and base URL.`
187+
detailedMessage = `Connection failed. Please check your network connection and base URL.`
197188
}
189+
198190
yield {
199191
type: "error",
200192
error: errorType,

0 commit comments

Comments
 (0)