Przeglądaj źródła

chore(runtime): standardized error message (#536)

* chore(runtime): standardized error message

* fix(runtime): test errors
Louis Young 5 miesięcy temu
rodzic
commit
f665312feb

+ 4 - 0
apps/demo-free-layout/src/components/testrun/node-status-bar/render/index.module.less

@@ -26,6 +26,10 @@
   }
 }
 
+.round {
+  border-radius: 50%;
+}
+
 .desc {
   margin: 0;
 }

+ 1 - 1
apps/demo-free-layout/src/components/testrun/node-status-bar/render/index.tsx

@@ -57,7 +57,7 @@ export const NodeStatusRender: FC<NodeStatusRenderProps> = ({ report }) => {
     if (isNodeSucceed) {
       return <IconSuccessFill />;
     }
-    return <IconWarningFill className={tagColor} />;
+    return <IconWarningFill className={classnames(tagColor, styles.round)} />;
   };
   const renderDesc = () => {
     const getDesc = () => {

+ 1 - 1
packages/runtime/js-core/src/domain/document/document/create-store.test.ts

@@ -109,6 +109,6 @@ describe('createStore', () => {
         nodeBlocks: new Map(),
         nodeEdges: new Map(),
       })
-    ).toThrow('invalid edge schema ID');
+    ).toThrow('Invalid edge schema ID');
   });
 });

+ 1 - 1
packages/runtime/js-core/src/domain/document/document/create-store.ts

@@ -100,7 +100,7 @@ export const createStore = (params: FlattenData): DocumentStore => {
     const from = store.nodes.get(sourceNodeID);
     const to = store.nodes.get(targetNodeID);
     if (!from || !to) {
-      throw new Error(`invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
+      throw new Error(`Invalid edge schema ID: ${id}, from: ${sourceNodeID}, to: ${targetNodeID}`);
     }
     const edge = createEdge(store, {
       id,

+ 3 - 4
packages/runtime/js-core/src/domain/engine/index.ts

@@ -141,7 +141,7 @@ export class WorkflowRuntimeEngine implements IEngine {
     }
     const targetPort = node.ports.outputs.find((port) => port.id === branch);
     if (!targetPort) {
-      throw new Error(`branch ${branch} not found`);
+      throw new Error(`Engine branch ${branch} not found`);
     }
     const nextNodeIDs: Set<string> = new Set(targetPort.edges.map((edge) => edge.to.id));
     const nextNodes = allNextNodes.filter((nextNode) => nextNodeIDs.has(nextNode.id));
@@ -157,12 +157,11 @@ export class WorkflowRuntimeEngine implements IEngine {
 
   private async executeNext(params: { context: IContext; node: INode; nextNodes: INode[] }) {
     const { context, node, nextNodes } = params;
-    if (node.type === FlowGramNode.End) {
+    if (node.type === FlowGramNode.End || node.type === FlowGramNode.BlockEnd) {
       return;
     }
     if (nextNodes.length === 0) {
-      // throw new Error(`node ${node.id} has no next nodes`); // inside loop node may have no next nodes
-      return;
+      throw new Error(`Node ${node.id} has no next nodes`); // inside loop node may have no next nodes
     }
     await Promise.all(
       nextNodes.map((nextNode) =>

+ 1 - 1
packages/runtime/js-core/src/domain/executor/index.ts

@@ -30,7 +30,7 @@ export class WorkflowRuntimeExecutor implements IExecutor {
     const nodeType = context.node.type;
     const nodeExecutor = this.nodeExecutors.get(nodeType);
     if (!nodeExecutor) {
-      throw new Error(`no executor found for node type ${nodeType}`);
+      throw new Error(`No executor found for node type ${nodeType}`);
     }
     const output = await nodeExecutor.execute(context);
     return output;

+ 4 - 4
packages/runtime/js-core/src/domain/state/index.ts

@@ -89,7 +89,7 @@ export class WorkflowRuntimeState implements IState {
 
   public parseRef<T = unknown>(ref: IFlowRefValue): IVariableParseResult<T> | null {
     if (ref?.type !== 'ref') {
-      throw new Error(`invalid ref value: ${ref}`);
+      throw new Error(`Invalid ref value: ${ref}`);
     }
     if (!ref.content || ref.content.length < 2) {
       return null;
@@ -108,7 +108,7 @@ export class WorkflowRuntimeState implements IState {
 
   public parseTemplate(template: IFlowTemplateValue): IVariableParseResult<string> | null {
     if (template?.type !== 'template') {
-      throw new Error(`invalid template value: ${template}`);
+      throw new Error(`Invalid template value: ${template}`);
     }
     if (!template.content) {
       return null;
@@ -139,7 +139,7 @@ export class WorkflowRuntimeState implements IState {
 
   public parseValue<T = unknown>(flowValue: IFlowValue): IVariableParseResult<T> | null {
     if (!flowValue?.type) {
-      throw new Error(`invalid flow value type: ${(flowValue as any).type}`);
+      throw new Error(`Invalid flow value type: ${(flowValue as any).type}`);
     }
     // constant
     if (flowValue.type === 'constant') {
@@ -162,7 +162,7 @@ export class WorkflowRuntimeState implements IState {
       return this.parseTemplate(flowValue) as IVariableParseResult<T> | null;
     }
     // unknown type
-    throw new Error(`unknown flow value type: ${(flowValue as any).type}`);
+    throw new Error(`Unknown flow value type: ${(flowValue as any).type}`);
   }
 
   public isExecutedNode(node: INode): boolean {

+ 4 - 4
packages/runtime/js-core/src/infrastructure/utils/runtime-type.test.ts

@@ -427,21 +427,21 @@ describe('WorkflowRuntimeType', () => {
       it('should throw error for String and Number mix', () => {
         const types = [WorkflowVariableType.String, WorkflowVariableType.Number];
         expect(() => WorkflowRuntimeType.getArrayItemsType(types)).toThrow(
-          'array items type must be same, expect string, but got number'
+          'Array items type must be same, expect string, but got number'
         );
       });
 
       it('should throw error for Boolean and String mix', () => {
         const types = [WorkflowVariableType.Boolean, WorkflowVariableType.String];
         expect(() => WorkflowRuntimeType.getArrayItemsType(types)).toThrow(
-          'array items type must be same, expect boolean, but got string'
+          'Array items type must be same, expect boolean, but got string'
         );
       });
 
       it('should throw error for Object and Array mix', () => {
         const types = [WorkflowVariableType.Object, WorkflowVariableType.Array];
         expect(() => WorkflowRuntimeType.getArrayItemsType(types)).toThrow(
-          'array items type must be same, expect object, but got array'
+          'Array items type must be same, expect object, but got array'
         );
       });
 
@@ -452,7 +452,7 @@ describe('WorkflowRuntimeType', () => {
           WorkflowVariableType.Boolean,
         ];
         expect(() => WorkflowRuntimeType.getArrayItemsType(types)).toThrow(
-          'array items type must be same, expect string, but got number'
+          'Array items type must be same, expect string, but got number'
         );
       });
     });

+ 1 - 1
packages/runtime/js-core/src/infrastructure/utils/runtime-type.ts

@@ -67,7 +67,7 @@ export namespace WorkflowRuntimeType {
     const expectedType = types[0];
     types.forEach((type) => {
       if (type !== expectedType) {
-        throw new Error(`array items type must be same, expect ${expectedType}, but got ${type}`);
+        throw new Error(`Array items type must be same, expect ${expectedType}, but got ${type}`);
       }
     });
     return expectedType;

+ 6 - 5
packages/runtime/js-core/src/nodes/condition/index.ts

@@ -32,7 +32,7 @@ export class ConditionExecutor implements INodeExecutor {
       .filter((item) => this.checkCondition(item));
     const activatedCondition = parsedConditions.find((item) => this.handleCondition(item));
     if (!activatedCondition) {
-      throw new Error('no condition is activated');
+      throw new Error('No condition is activated');
     }
     return {
       outputs: {},
@@ -62,14 +62,15 @@ export class ConditionExecutor implements INodeExecutor {
   private checkCondition(condition: ConditionValue): boolean {
     const rule = conditionRules[condition.leftType];
     if (isNil(rule)) {
-      throw new Error(`left type "${condition.leftType}" is not supported`);
+      throw new Error(`Condition left type "${condition.leftType}" is not supported`);
     }
     const ruleType = rule[condition.operator];
     if (isNil(ruleType)) {
-      throw new Error(`left type "${condition.leftType}" has no operator "${condition.operator}"`);
+      throw new Error(
+        `Condition left type "${condition.leftType}" has no operator "${condition.operator}"`
+      );
     }
     if (ruleType !== condition.rightType) {
-      // throw new Error(`condition right type expected ${ruleType}, got ${condition.rightType}`);
       return false;
     }
     return true;
@@ -78,7 +79,7 @@ export class ConditionExecutor implements INodeExecutor {
   private handleCondition(condition: ConditionValue): boolean {
     const handler = conditionHandlers[condition.leftType];
     if (!handler) {
-      throw new Error(`condition left type ${condition.leftType} is not supported`);
+      throw new Error(`Condition left type ${condition.leftType} is not supported`);
     }
     const isActive = handler(condition);
     return isActive;

+ 16 - 6
packages/runtime/js-core/src/nodes/llm/index.ts

@@ -49,7 +49,17 @@ export class LLMExecutor implements INodeExecutor {
     }
     messages.push(new HumanMessage(prompt));
 
-    const apiMessage = await model.invoke(messages);
+    let apiMessage;
+    try {
+      apiMessage = await model.invoke(messages);
+    } catch (error) {
+      // 调用 LLM API 失败
+      const errorMessage = (error as Error)?.message;
+      if (errorMessage === 'Connection error.') {
+        throw new Error(`Network error: unreachable api "${apiHost}"`);
+      }
+      throw error;
+    }
 
     const result = apiMessage.content;
     return {
@@ -63,14 +73,14 @@ export class LLMExecutor implements INodeExecutor {
     const { modelName, temperature, apiKey, apiHost, prompt } = inputs;
     const missingInputs = [];
 
-    if (isNil(modelName)) missingInputs.push('modelName');
+    if (!modelName) missingInputs.push('modelName');
     if (isNil(temperature)) missingInputs.push('temperature');
-    if (isNil(apiKey)) missingInputs.push('apiKey');
-    if (isNil(apiHost)) missingInputs.push('apiHost');
-    if (isNil(prompt)) missingInputs.push('prompt');
+    if (!apiKey) missingInputs.push('apiKey');
+    if (!apiHost) missingInputs.push('apiHost');
+    if (!prompt) missingInputs.push('prompt');
 
     if (missingInputs.length > 0) {
-      throw new Error(`LLM node missing required inputs: ${missingInputs.join(', ')}`);
+      throw new Error(`LLM node missing required inputs: "${missingInputs.join('", "')}"`);
     }
 
     // Validate apiHost format before checking existence

+ 5 - 5
packages/runtime/js-core/src/nodes/loop/index.ts

@@ -40,7 +40,7 @@ export class LoopExecutor implements INodeExecutor {
     const blockStartNode = subNodes.find((node) => node.type === FlowGramNode.BlockStart);
 
     if (!blockStartNode) {
-      throw new Error('block start node not found');
+      throw new Error('Loop block start node not found');
     }
 
     const blockOutputs: LoopOutputs[] = [];
@@ -67,7 +67,7 @@ export class LoopExecutor implements INodeExecutor {
           node: blockStartNode,
         });
       } catch (e) {
-        throw new Error(`loop block execute error`);
+        throw new Error(`Loop block execute error`);
       }
       const blockOutput = this.getBlockOutput(context, subContext);
       blockOutputs.push(blockOutput);
@@ -99,15 +99,15 @@ export class LoopExecutor implements INodeExecutor {
   private checkLoopArray(LoopArrayVariable: IVariableParseResult<LoopArray> | null): void {
     const loopArray = LoopArrayVariable?.value;
     if (!loopArray || isNil(loopArray) || !Array.isArray(loopArray)) {
-      throw new Error('loopFor is required');
+      throw new Error('Loop "loopFor" is required');
     }
     const loopArrayType = LoopArrayVariable.type;
     if (loopArrayType !== WorkflowVariableType.Array) {
-      throw new Error('loopFor must be an array');
+      throw new Error('Loop "loopFor" must be an array');
     }
     const loopArrayItemType = LoopArrayVariable.itemsType;
     if (isNil(loopArrayItemType)) {
-      throw new Error('loopFor items must be array items');
+      throw new Error('Loop "loopFor.items" must be array items');
     }
   }