Skip to content

fix: [AI-Generated Earnings] The AI-generated earnings feature modifies earnings directly, without first presenting the proposed adjustments to the user for their review and confirmation. - #7743

Closed
shaohuzhang1 wants to merge 2 commits into
v3from
pr@v3@fix_ai_gen

Conversation

@shaohuzhang1

Copy link
Copy Markdown
Contributor

fix: [Agent] When adding conversation logs to the knowledge base, the added structural content is empty.
fix: [AI-Generated Earnings] The AI-generated earnings feature modifies earnings directly, without first presenting the proposed adjustments to the user for their review and confirmation.

…es earnings directly, without first presenting the proposed adjustments to the user for their review and confirmation.
if (!canvasChanged) return
await layoutCanvas(lf)
lf.fitView()
MsgSuccess('工作流已生成,请检查后保存')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Issues and Suggestions:

  1. Syntax/Formatting Error in Diff (Critical)

    • Line 2: ref, useTemplateRef ,provide} has an extra space before the comma: useTemplateRef ,provide. While valid JS, it violates standard formatting rules (e.g., ESLint/Prettier).
    • Line 23: import { VIEW_KEY,VIEW_TYPE } from ... lacks a space after the comment and before the import statement (import type AgentEvent... on line 22 vs line 23). It also misses a blank line separator often expected by linters.
  2. Unused Variable / Dead Code Risk

    • provide(VIEW_KEY, VIEW_TYPE.DEBUG) is called at the top level of <script setup>. Ensure that this provide is necessary for this component. If WorkflowAiGeneratePanel is not a parent of the components relying on VIEW_KEY, this will have no effect or be misleading. If it is intended, ensure VIEW_KEY and VIEW_TYPE are correctly exported from @/conversation-panel/view.
  3. Logic Flaw in onEvent Condition

    • !event.position.length: If position is not an array (e.g., undefined or null), this will throw TypeError: Cannot read properties of undefined (reading 'length').
    • Fix: Use optional chaining: !event.position?.length or check truthiness if empty array should trigger it differently. However, based on context, you likely want to detect if position is empty/missing indicating it's not a sub-tool event. If position is always supposed to be an array, initialize it. If it can be undefined, use !event.position || !event.position.length.
  4. State Management Issue (canvasChanged)

    • let canvasChanged = false; inside <script setup> works because module-level variables in <script setup> are isolated per component instance. However, if run() is called multiple times concurrently (race condition), they share the same canvasChanged flag. This could lead to incorrect state if two runs overlap.
    • Suggestion: Make canvasChanged local to the run function scope by passing it via closure or resetting it properly and ensuring sequential execution, or move it into activeIdsMap or similar state tracking if concurrent runs are possible.
  5. Optimization/Readability: Set Declaration

    • const CANVAS_MUTATING_TOOLS = new Set([...]) is declared inside the script but outside any function. This is fine and efficient (created once per component setup). No issue, just good practice confirmed.
  6. Missing Error Handling Edge Case

    • In run(), if result.status !== 'done' (e.g., error/cancelled), status.value remains 'running' until manually reset elsewhere? Check how errors are handled. The current code only updates status on 'done'. Ensure failure states reset status.value appropriately in other branches not shown.
  7. Import Sorting/Linter

    • provide is added but placed incorrectly relative to alphabet/standard order (ref, useTemplateRef ,provide → should be provide, ref, useTemplateRef). Some linters enforce alphabetical order.

✅ Recommended Fixed Code Snippet (Diff Style)

     @@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { computed, inject, nextTick, onBeforeUnmount, ref, useTemplateRef } from 'vue'
+import { computed, inject, provide, nextTick, onBeforeUnmount, ref, useTemplateRef } from 'vue' // Alphabetical order
 import { useRoute } from 'vue-router'
 import { cloneDeep } from 'lodash'
 import type LogicFlow from '@logicflow/core'
@@ -18,8 +18,9 @@ import { clearDebugLog } from '../common'
 import { createAgent } from '../create-agent'
 import type { AgentEvent, AgentHandle } from '../type'
 import { createActivity } from './activity'
-
+// Add blank line if required by linter, fix spacing
+import { VIEW_KEY, VIEW_TYPE } from '@/conversation-panel/view'
 // Provide view key for debugging context (ensure parent-child relationship exists)
 provide(VIEW_KEY, VIEW_TYPE.DEBUG)
 
 defineOptions({ name: 'WorkflowAiGeneratePanel' })
 const props = withDefaults(
   defineProps<{
@@ -90,9 +91,9 @@ function scrollToBottom() {
   nextTick(() => scrollbarRef.value?.setScrollTop(Number.MAX_SAFE_INTEGER))
 }
 
-/** 会改动画布的顶层工具;本轮未调用时视为仅规划 / 答复(如等待用户确认方案),不提示生成完成 */
+/** Tools that mutate canvas. If none are called, skip layout/fitView (e.g., planning-only mode) */
 const CANVAS_MUTATING_TOOLS = new Set(['add_node', 'add_edge', 'delete_node', 'delete_edge', 'configure_node', 'layout'])
 let canvasChanged = false
 
 /** Top-level agent summary flows into activity as text; failures shown as FAILURE items */
 function onEvent(event: AgentEvent) {
-  if (event.type === 'tool_start' && !event.position.length && CANVAS_MUTATING_TOOLS.has(event.name)) canvasChanged = true
+  // Safe check: handle if position is undefined/null
+  const isTopLevel = !event.position || event.position.length === 0
+  if (event.type === 'tool_start' && isTopLevel && CANVAS_MUTATING_TOOLS.has(event.name)) {
+    canvasChanged = true
+  }
   activity.pushEvent(event)
   scrollToBottom()
 }
@@ -117,12 +123,14 @@ async function run(text: string) {
     )
   }
   status.value = 'running'
   canvasChanged = false
   const result = await agent.run(text)
   if (result.status === 'done') {
     status.value = 'done'
     // Only fit view and show success if canvas actually changed
-    if (!canvasChanged) return
+    if (!canvasChanged) {
+      // Optional: Show a different message or do nothing silently if only planning occurred
+      return 
+    }
     await layoutCanvas(lf)
     lf.fitView()
     MsgSuccess('工作流已生成,请检查后保存')

problem, _ = Problem.objects.get_or_create(content=problem_text, knowledge_id=knowledge_id)
problem_paragraph_mapping = ProblemParagraphMapping(
id=uuid.uuid7(),
knowledge_id=knowledge_id,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of the diff

1. Potential None / empty-string issues

  • chat_record.question.get("content") — if question is None, or the dict has no "content" key, this raises AttributeError/KeyError. Use defensive access:
    problem_text = (chat_record.question or {}).get("content") or ""
  • "".join([r.get("content") for r in chat_record.messages if r.get("type") == "TEXT"]) — r.get("content") can return None, which will raise TypeError: sequence item N: expected str instance, NoneType found. Filter or coerce:
    answer_text = "".join(
        r["content"] for r in chat_record.messages
        if r.get("type") == "TEXT" and r.get("content")
    )

2. Behavior change worth confirming

  • The previous code used chat_record.answer_text / chat_record.problem_text (presumably denormalized fields or properties on the model). The new code recomputes them from messages / question. Make sure:
    • chat_record.messages is always populated and ordered correctly.
    • Concatenating multiple TEXT messages without a separator ("") is intentional — otherwise use "\n".
    • Any whitespace/None values are trimmed before chunking/storing.

3. Performance

  • Two list comprehensions iterate over messages twice per chat record. Minor, but could be collapsed if both fields are needed. Not critical here.
  • Problem.objects.get_or_create(content=problem_text, ...) will now run with potentially different problem_text than before. If problem_text computation differs from what's stored in chat_record.problem_text, deduplication behavior changes. Ensure DB uniqueness constraints include knowledge_id.
  • Inside a loop, get_or_create and Paragraph construction mean N+1 writes; consider bulk_create for Paragraph / ProblemParagraphMapping batches and pre-fetching existing Problem rows with content__in=[...] to avoid repeated queries.

4. Empty data handling

  • If answer_text or problem_text ends up as "", you'll create empty Paragraph/Problem rows. Consider skipping records where answer_text.strip() == "" (or raising) to avoid polluting DB.

5. Style / type safety

  • The double iteration with a generator expression is fine, but typing could catch these issues earlier. Consider annotating chat_record.messages as List[Dict[str, str]] and letting mypy flag Optional[str] joins.
  • uuid.uuid7() — confirm your Python/dependency version actually provides uuid7 (not in stdlib until recently); otherwise this is a latent runtime error unrelated to this diff.

6. Recommend refactor

for chat_record in chat_record_list:
    messages = chat_record.messages or []
    answer_text = "\n".join(
        m["content"] for m in messages
        if m.get("type") == "TEXT" and isinstance(m.get("content"), str)
    )
    problem_text = ((chat_record.question or {}).get("content") or "").strip()
    if not answer_text or not problem_text:
        continue  # or log/skip
    ...

Summary: Main risks are None propagation into "".join, possible unintended semantics change (concatenation without separator), and DB-level empty rows / dedup regressions from recomputed problem_text. The core logic is sound; harden input handling and verify uniqueness assumptions.

@shaohuzhang1
shaohuzhang1 deleted the pr@v3@fix_ai_gen branch November 9, 2026 06:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant