Ma77Ball commented on code in PR #8637:
URL: https://github.com/apache/texera/pull/8637#discussion_r4107521354


##########
demo-workflows/README.md:
##########
@@ -0,0 +1,59 @@
+# Demo workflows for the AI Workflow Fixer

Review Comment:
   This README has no Apache license header, so the `check-header` CI job 
(`apache/skywalking-eyes`) will fail on this PR.
   
   Why it matters: `.licenserc.yaml` ignores `**/*.json` but not `**/*.md`, and 
every existing README.md in the repo carries the ASF header. Without it, the 
required license check goes red and blocks merge. (If you drop the folder per 
the other comment, this is moot.)
   
   Suggested fix:
   
   ```suggestion
   <!--
     ~ Licensed to the Apache Software Foundation (ASF) under one
     ~ or more contributor license agreements.  See the NOTICE file
     ~ distributed with this work for additional information
     ~ regarding copyright ownership.  The ASF licenses this file
     ~ to you 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.
   -->
   
   # Demo workflows for the AI Workflow Fixer
   ```



##########
demo-workflows/combined.json:
##########
@@ -0,0 +1,92 @@
+{

Review Comment:
   This PR adds a new top-level `demo-workflows/` directory (README + 7 JSON 
workflows, ~800 lines) that no test, build, or runtime code references.
   
   Why it matters: it roughly doubles the PR's line count with material 
unrelated to the feature code, 5 of the 7 files need the external movies 
dataset to run, and it adds a new top-level path to the repo for demo 
artifacts. The JSON operator shapes will also drift from the engine over time 
with nothing to catch it.
   
   Suggested fix: drop the folder from this PR (you already offered to). If you 
want runnable examples kept, the README notes they share the shape of 
`bin/single-node/examples/workflows/`, so move them there rather than adding a 
new top-level directory. Not a functional blocker, but it should not merge 
as-is at repo root.



##########
frontend/src/app/workspace/component/result-panel/ai-fix/ai-fix-frame.component.ts:
##########
@@ -0,0 +1,132 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { Component, Input } from "@angular/core";
+import { AsyncPipe, NgFor, NgIf } from "@angular/common";
+import { Observable } from "rxjs";
+import { NzAlertComponent } from "ng-zorro-antd/alert";
+import { NzSpinComponent } from "ng-zorro-antd/spin";
+import { NzTagComponent } from "ng-zorro-antd/tag";
+import { NzButtonComponent } from "ng-zorro-antd/button";
+import { NzWaveDirective } from "ng-zorro-antd/core/wave";
+import { NzIconDirective } from "ng-zorro-antd/icon";
+import { ExecuteWorkflowService } from 
"../../../service/execute-workflow/execute-workflow.service";
+import { WorkflowActionService } from 
"../../../service/workflow-graph/model/workflow-action.service";
+import { WorkflowCompilingService } from 
"../../../service/compile-workflow/workflow-compiling.service";
+import { WorkflowConsoleService } from 
"../../../service/workflow-console/workflow-console.service";
+import { AiWorkflowFixerService, FixConfidence, FixState, UNSUPPORTED_MESSAGE 
} from "./ai-workflow-fixer.service";
+
+/**
+ * Result-panel frame that asks the LLM to fix the failed operator.
+ *
+ * The analysis is started by an explicit button: ResultPanelComponent rebuilds
+ * every frame on each re-render (clearResultPanel), so analysing on init would
+ * fire a model request per re-render. For the same reason all state lives in
+ * AiWorkflowFixerService and this component keeps none.
+ */
+@Component({
+  selector: "texera-ai-fix-frame",
+  templateUrl: "./ai-fix-frame.component.html",
+  styleUrls: ["./ai-fix-frame.component.scss"],
+  imports: [
+    NgIf,
+    NgFor,
+    AsyncPipe,
+    NzAlertComponent,
+    NzSpinComponent,
+    NzTagComponent,
+    NzButtonComponent,
+    NzWaveDirective,
+    NzIconDirective,
+  ],
+})
+export class AiFixFrameComponent {
+  @Input() operatorId?: string;
+
+  public readonly state$: Observable<FixState>;
+  public readonly unsupportedMessage = UNSUPPORTED_MESSAGE;
+
+  constructor(
+    private aiWorkflowFixerService: AiWorkflowFixerService,
+    private executeWorkflowService: ExecuteWorkflowService,
+    private workflowActionService: WorkflowActionService,
+    private workflowCompilingService: WorkflowCompilingService,
+    private workflowConsoleService: WorkflowConsoleService
+  ) {
+    this.state$ = aiWorkflowFixerService.getState$();

Review Comment:
   The frame binds the shared service state without checking it belongs to this 
frame's operator, so a suggestion analyzed for one operator can be shown and 
applied under another.
   
   Why it matters: `AiWorkflowFixerService` is a root singleton with one 
`stateSubject`, and the template renders `state$ | async` with no comparison to 
`@Input() operatorId`. Concrete sequence: two Python UDFs in a pipeline both 
raise an ERROR console message, so both get an AI Fix tab. Analyze operator A 
-> state = {operatorId: A, status: ready, suggestedFix: fixA}. Select operator 
B: the panel rebuilds the frame with `operatorId = B`, but `state$` still holds 
A's ready suggestion, so B's tab shows A's root cause and diff. Clicking Apply 
calls `applyFix()`, which reads `current.operatorId` (= A) and patches operator 
A while the user is looking at B, then re-runs. The write is undoable but 
targets the wrong operator.
   
   Suggested fix: gate the ready/diff UI and the Apply/Discard actions on 
`state.operatorId === operatorId` (expose a guarded observable, or reset the 
service to idle when the bound `operatorId` differs from `state.operatorId`). 
This is a multi-spot change across the component and template, not a one-click 
edit. Worth a spec that analyzes one operator and asserts a second operator's 
frame does not show or apply it.



##########
frontend/src/app/workspace/component/result-panel/ai-fix/ai-workflow-fixer.service.ts:
##########
@@ -0,0 +1,249 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { Injectable } from "@angular/core";
+import { BehaviorSubject, Observable } from "rxjs";
+import { createOpenAI } from "@ai-sdk/openai";
+import { generateText, type ModelMessage } from "ai";
+import { AppSettings } from "../../../../common/app-setting";
+import { AuthService } from "../../../../common/service/user/auth.service";
+import { WorkflowActionService } from 
"../../../service/workflow-graph/model/workflow-action.service";
+import { ExecuteWorkflowService } from 
"../../../service/execute-workflow/execute-workflow.service";
+import { WarehouseService } from 
"../../../../common/service/warehouse/warehouse.service";
+import { GuiConfigService } from 
"../../../../common/service/gui-config.service";
+import { PortSchema } from "../../../types/workflow-compiling.interface";
+import { buildFixPrompt } from "./ai-fix-prompt";
+
+export type FixErrorType = "missing_column" | "type_error" | "null_error" | 
"model_not_found" | "unsupported";
+export type FixConfidence = "high" | "medium" | "low";
+
+export interface SuggestedFix {
+  type: "code_change" | "property_change";
+  original: string;
+  suggested: string;
+  explanation: string;
+  // Rendered as the confidence badge; `fieldName` names the patched property
+  // (property_change only) so applyFix knows which field to write.
+  confidence: FixConfidence;
+  fieldName?: string;
+}
+
+export interface FixState {
+  operatorId: string;
+  errorMessage: string;
+  errorType: FixErrorType;
+  originalCode?: string;
+  originalProperties?: object;
+  suggestedFix?: SuggestedFix;
+  // `applied_without_run`: the fix was written but the deployment refused to 
start a
+  // run (a warehouse is required and none is selected), so the panel must not 
claim one.
+  status: "idle" | "analyzing" | "ready" | "applying" | "applied" | 
"applied_without_run" | "error";
+}
+
+// Model id as LiteLLM exposes it (bin/single-node/litellm-config.yaml), not 
the provider's id.
+export const AI_FIXER_MODEL = "claude-haiku-4.5";

Review Comment:
   `AI_FIXER_MODEL` is hard-coded to the LiteLLM alias `claude-haiku-4.5`.
   
   Why it matters: any deployment whose `litellm-config.yaml` exposes a 
different alias (or renames this one) gets a 404 from the proxy, which surfaces 
as the generic "The AI could not suggest a fix" error with no hint that the 
cause is a model-name mismatch. The frontend constant is silently coupled to 
backend config.
   
   Suggested fix: source the model id from `GuiConfig`/env (the same place 
`copilotEnabled` comes from) instead of a compile-time constant, so it tracks 
the proxy config. Optional, but it will bite a differently-configured 
deployment.



##########
frontend/src/app/workspace/component/result-panel/ai-fix/ai-workflow-fixer.service.ts:
##########
@@ -0,0 +1,249 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import { Injectable } from "@angular/core";
+import { BehaviorSubject, Observable } from "rxjs";
+import { createOpenAI } from "@ai-sdk/openai";
+import { generateText, type ModelMessage } from "ai";
+import { AppSettings } from "../../../../common/app-setting";
+import { AuthService } from "../../../../common/service/user/auth.service";
+import { WorkflowActionService } from 
"../../../service/workflow-graph/model/workflow-action.service";
+import { ExecuteWorkflowService } from 
"../../../service/execute-workflow/execute-workflow.service";
+import { WarehouseService } from 
"../../../../common/service/warehouse/warehouse.service";
+import { GuiConfigService } from 
"../../../../common/service/gui-config.service";
+import { PortSchema } from "../../../types/workflow-compiling.interface";
+import { buildFixPrompt } from "./ai-fix-prompt";
+
+export type FixErrorType = "missing_column" | "type_error" | "null_error" | 
"model_not_found" | "unsupported";
+export type FixConfidence = "high" | "medium" | "low";
+
+export interface SuggestedFix {
+  type: "code_change" | "property_change";
+  original: string;
+  suggested: string;
+  explanation: string;
+  // Rendered as the confidence badge; `fieldName` names the patched property
+  // (property_change only) so applyFix knows which field to write.
+  confidence: FixConfidence;
+  fieldName?: string;
+}
+
+export interface FixState {
+  operatorId: string;
+  errorMessage: string;
+  errorType: FixErrorType;
+  originalCode?: string;
+  originalProperties?: object;
+  suggestedFix?: SuggestedFix;
+  // `applied_without_run`: the fix was written but the deployment refused to 
start a
+  // run (a warehouse is required and none is selected), so the panel must not 
claim one.
+  status: "idle" | "analyzing" | "ready" | "applying" | "applied" | 
"applied_without_run" | "error";
+}
+
+// Model id as LiteLLM exposes it (bin/single-node/litellm-config.yaml), not 
the provider's id.
+export const AI_FIXER_MODEL = "claude-haiku-4.5";
+export const AI_FIXER_TIMEOUT_MS = 120_000;
+export const UNSUPPORTED_MESSAGE = "This error type is not yet supported for 
automatic fixing.";
+
+const IDLE_STATE: FixState = { operatorId: "", errorMessage: "", errorType: 
"unsupported", status: "idle" };
+
+// Each pattern matches a line anywhere inside a traceback, not the whole 
message.
+const PATTERNS: ReadonlyArray<[RegExp, FixErrorType]> = [
+  [/KeyError:\s*['"][^'"]+['"]/, "missing_column"],
+  [/TypeError:\s*unsupported operand/i, "type_error"],
+  [/ValueError:[^\n]*\bNaN\b|NullPointerException/i, "null_error"],
+  [/ModelNotFound|\b404\b/i, "model_not_found"],

Review Comment:
   The `model_not_found` pattern matches a bare `404` anywhere in the traceback 
via `\b404\b`.
   
   Why it matters: a traceback that merely contains `404` (a line number, an 
HTTP status from unrelated user code, a literal in the data) is classified as 
`model_not_found` and sent to the model asking it to correct the model config 
field, producing an irrelevant `property_change` suggestion. Low harm (one 
wrong call), but the false-positive surface is wide.
   
   Suggested fix: tie `404` to the model-lookup context rather than matching it 
standalone (require it alongside a model / not-found token, or match the 
specific message shape LiteLLM returns). Prose only: I do not know the exact 
upstream error strings to give a precise regex.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to