# Cvss向量代理分析错误
_Exported on 2025/12/26 at GMT+8 19:15:55 from Cursor (2.2.43)_

---

**User**

cvss vector agent分析cve的时候运行报错，帮我分析一下这个问题，错误日志如下：

2025-12-24 12:07:16|x-sec|INFO|./core/agents/mimora/cvss_vector_agent.py:504|_generate_cvss_vector|开始完成CVE风险评估并生成CVSS向量
2025-12-24 12:07:16|x-sec|INFO|./core/agents/mimora/cvss_vector_agent.py:401|_normalize_cvss_data|开始归一化CVSS向量数据
2025-12-24 12:07:26|x-sec|INFO|./core/agents/mimora/cvss_vector_agent.py:527|_generate_cvss_vector|Generated CVSS Vector Response
2025-12-24 12:07:26|x-sec|DEBUG|./core/agents/mimora/cvss_vector_agent.py:528|_generate_cvss_vector|Response content: {
    "cvss_vector": "CVSS:3.1/AV:L/AC:L
2025-12-24 12:07:26|x-sec|INFO|./core/agents/mimora/tools/tool_utils.py:19|wrapper|开始执行工具: _calculate_cvss_score, 参数: ('CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H',), {}
2025-12-24 12:07:26|x-sec|INFO|./core/agents/mimora/tools/cvss_tool.py:107|_calculate_cvss_score|开始计算CVSS评分: version=3.0, vector=CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H...
2025-12-24 12:07:26|x-sec|INFO|./core/agents/mimora/tools/cvss_tool.py:170|_calculate_cvss_score|成功计算CVSS评分: version=3.1, base_score=7.8, base_severity=High
2025-12-24 12:07:26|x-sec|INFO|./core/agents/mimora/tools/tool_utils.py:22|wrapper|工具 _calculate_cvss_score 执行成功，耗时: 0.00s
2025-12-24 12:07:26|x-sec|INFO|./core/agents/mimora/cvss_vector_agent.py:504|_generate_cvss_vector|开始完成CVE风险评估并生成CVSS向量
2025-12-24 12:07:26|x-sec|INFO|./core/agents/mimora/cvss_vector_agent.py:568|_generate_cvss_severity|开始生成CVSS严重性等级
2025-12-24 12:07:35|x-sec|INFO|./core/agents/mimora/cvss_vector_agent.py:527|_generate_cvss_vector|Generated CVSS Vector Response
2025-12-24 12:07:35|x-sec|DEBUG|./core/agents/mimora/cvss_vector_agent.py:528|_generate_cvss_vector|Response content: {
    "cvss_vector": "CVSS:3.1/AV:L/AC:L
2025-12-24 12:07:35|x-sec|INFO|./core/agents/mimora/tools/tool_utils.py:19|wrapper|开始执行工具: _calculate_cvss_score, 参数: ('CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H',), {}
2025-12-24 12:07:35|x-sec|INFO|./core/agents/mimora/tools/cvss_tool.py:107|_calculate_cvss_score|开始计算CVSS评分: version=3.0, vector=CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H...
2025-12-24 12:07:35|x-sec|INFO|./core/agents/mimora/tools/cvss_tool.py:170|_calculate_cvss_score|成功计算CVSS评分: version=3.1, base_score=7.8, base_severity=High
2025-12-24 12:07:35|x-sec|INFO|./core/agents/mimora/tools/tool_utils.py:22|wrapper|工具 _calculate_cvss_score 执行成功，耗时: 0.00s
2025-12-24 12:07:42|x-sec|INFO|./core/agents/mimora/cvss_vector_agent.py:596|_generate_cvss_severity|Generated CVSS Severity Response
2025-12-24 12:07:42|x-sec|DEBUG|./core/agents/mimora/cvss_vector_agent.py:597|_generate_cvss_severity|Response content: {
    "cvss_severity": "Important",
    "severity_statement": "修正后的CVSS向量（CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H）与原始CVSS向量（CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H）在语义和评分维度上完全一致，仅版本号由3.0更新为3.1，而CVSS 3.1对3.0中该>类向量的计算逻辑未作实质性修改。根据CVSS 3.1标准规范，该向量对应的基分约为7.8，落在7.0~8.9区间，属于Important等级。漏洞描述表明该漏洞存在于OpenEXR组件中，通过恶意EXR文件触发堆溢出实现远程代码执行，但明确要求用户交互（UI:R），
即受害者必须主动打开恶意文件或访问恶意页面，攻击向量为本地（AV:L），说明攻击者无法直接通过网络远程触发，而需诱导用户操作。ASD数据进一步佐证了该利用场景：APT29通过鱼叉式钓鱼邮件投递恶意附件（如ZIP内含恶意EXR或相关载体），依赖>用户解压并打开文件完成初始访问，符合T1566.001（Spearphishing Attachment）和T1203（Exploitation for Client Execution）的TTP模式。尽管可导致任意代码执行（C:H/I:H/A:H），但由于需要本地交互且非默认启用的核心系统服务（OpenEXR为图>像处理库，通常用于专业图形应用，非操作系统默认运行组件），不满足Critical等级中‘无需用户交互、网络可利用、影响默认服务’等条件。因此，维持原始Important严重性等级合理且符合CVSS 3.1规范。"
}
2025-12-24 12:07:42|x-sec|ERROR|./core/agents/mimora/cvss_vector_agent.py:725|cve_cvss_analysis|CVE CVSS分析失败: CVE-2025-12840, 错误: At key 'x_sec_cvss_vector': Can receive only one value per step. Use an Annotated key to handle multiple values.
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE
2025-12-24 12:07:42|x-sec|INFO|./server/app/api/routes/cve_analysis.py:70|execute_cve_analysis_with_queue|CVE CVSS分析执行完成: CVE-2025-12840

---

**Cursor**

```plaintext
At key 'x_sec_cvss_vector': Can receive only one value per step. Use an Annotated key to handle multiple values.
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE
```

```plaintext
- 🔧 修复 CVSS Vector Agent 并发写入冲突问题：添加 reducer 函数处理 LangGraph 并发更新，解决 generate_asd_data 和 normalize_cvss_data 节点并发执行时的状态冲突
```

从错误日志来看，这是一个 LangGraph 并发更新状态冲突问题。错误信息显示：

```
At key 'x_sec_cvss_vector': Can receive only one value per step. Use an Annotated key to handle multiple values.
```

让我查看 `cvss_vector_agent.py` 文件来分析具体的状态定义和图结构：

```plaintext
At key 'x_sec_cvss_vector': Can receive only one value per step. Use an Annotated key to handle multiple values.
```

```python
x_sec_cvss_vector: str = Field(..., description="x-sec cvss vector object")
```

```plaintext
12:07:26 开始完成CVE风险评估并生成CVSS向量  (generate_cvss_vector 第一次)


12:07:26 开始生成CVSS严重性等级  (generate_cvss_severity)
12:07:35 Generated CVSS Vector Response (generate_cvss_vector 第一次完成)
12:07:26 开始完成CVE风险评估并生成CVSS向量  (generate_cvss_vector 第二次?!)
```

我已经分析了问题。从错误日志和代码来看，问题出在以下几点：

## 问题分析

1. **`x_sec_cvss_vector` 字段缺少 reducer**：在 `CVSSVectorState` 中，`x_sec_cvss_vector` 没有使用 `Annotated` 配合 reducer 函数：

```70:71:core/agents/mimora/cvss_vector_agent.py
    x_sec_cvss_vector: str = Field(..., description="x-sec cvss vector object")
```

2. **类型定义错误**：`x_sec_cvss_vector` 被声明为 `str` 类型，但实际存储的是一个字典对象（在 `_generate_cvss_vector` 和 `_generate_cvss_severity` 中）。

3. **并发执行冲突**：从日志可以看到 `_generate_cvss_vector` 和 `_generate_cvss_severity` 都在更新 `x_sec_cvss_vector` 字段，由于 `normalize_cvss_data` 有多个入边（来自 `generate_asd_data`、`get_cvss_statement_data` 等），导致下游节点可能被多次触发。

## 解决方案

需要为 `x_sec_cvss_vector` 字段添加一个字典类型的 reducer 函数，并修正类型定义：

```python
    x_sec_cvss_vector: str = Field(..., description="x-sec cvss vector object")
```

```python
    x_sec_cvss_vector: str = Field(..., description="x-sec cvss vector object")
```

```python
"""
CVSS Vector Agent

CVSS Vector Agent负责根据CVE数据以及受影响产品组件的组件画像信息以及根据TTP模型建立的CVE ASD数据，差异化分析产品的CVE风险并依据CVSS 3.1标准重新生成CVSS向量和打分。
"""

import json
import re
from typing import Optional, List, Annotated

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_community.chat_models import ChatTongyi
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from pydantic import Field, field_validator


def _keep_non_empty_str_reducer(current: str, new: str) -> str:
    """字符串类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字段时，由于并发执行时reducer的调用顺序不确定，
    该函数采用"保留有内容的值"策略：
    - 如果current有内容，保留current
    - 如果current为空但new有内容，使用new
    - 如果都为空，返回空字符串

    这样确保有实际数据的节点产生的值不会被其他节点的空值覆盖。
    """
    # 优先保留有内容的数据
    if current and len(current.strip()) > 0:
        return current
    if new and len(new.strip()) > 0:
        return new
    return ""


def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    """字典类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字典字段时（如 x_sec_cvss_vector），
    该函数采用"合并更新"策略：
    - 以当前值为基础
    - 用新值覆盖/更新已有字段
    - 保留新值中新增的字段

    这样确保并发节点产生的数据可以正确合并，后执行的节点可以更新前面节点设置的值。
    """
    if current is None:
        current = {}
    if new is None:
        new = {}
    # 创建副本避免修改原对象
    result = dict(current)
    # 合并新值，新值覆盖旧值
    result.update(new)
    return result


from core.agents.mimora.tools.cvss_tool import _calculate_cvss_score
from core.agents.mimora.tools.cvss_data_tool import get_redhat_cve_details
from core.agents.mimora.data_type import LABEL_REDHAT, LABEL_VULAB, CVEData, CVSSData
from core.agents.mimora.cve_data_agent import CVEDataAgent, CVEDataState
from core.agents.mimora.mitre_asd_agent import MitreASDAgent
from core.models.llm_manager import X_SEC_LLM
from core.storage.db import CVECleanDBBase
from internal.log import get_logger
from internal.prompt_manager.prompts import CVSSVectorPrompts
from internal.thread_manager import generate_thread_id
from internal.config_manager import get_settings
from server.auth.api_key import get_db


class CVSSVectorState(MessagesState):
    cve_data: CVEData = Field(..., description="cve data")
    cvss_data: List[CVSSData] = Field(
        ..., description="cvss data list from cve source data"
    )
    cvss_vector: str = Field(..., description="cvss vector string")
    vulab_score: float = Field(..., description="vulab cvss score")
    vulab_severity: str = Field(..., description="vulab cvss severity")
    # 使用Annotated配合reducer处理并发更新，避免get_cvss_statement_data和normalize_cvss_data同时写入时的冲突
    statement_vector: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement vector string"
    )
    statement: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_asd_data和normalize_cvss_data同时写入时的冲突
    asd_data: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="asd data string"
    )
    x_sec_cvss_vector: str = Field(..., description="x-sec cvss vector object")


class CVSSVectorAgent:
    """CVSS Vector Agent"""

    def __init__(self):
        self.data_agent = CVEDataAgent()
        self.asd_agent = MitreASDAgent()
        self.llm = ChatTongyi(name="cvss-vector-agent-llm", model="qwen3-max")
        self.prompt_manager = CVSSVectorPrompts()
        self.memory = MemorySaver()
        self.agent = self._build_graph()
        self.logger = get_logger()

    def _build_graph(self):
        """构建CVSS Vector Agent的图结构

        工作流程：
        1. 并行获取需要的数据包括：cve data、cvss data、cve asd data
        2. 根据cvss data查看是否需要获取额外CVSS Statement数据
        3. 将上面获取的数据进行归一化
        4. 将归一化后的数据进行CVE风险评估与CVSS向量差异化评估

        图结构如下：
                                                               +-----------+
                                                             **| __start__ |**
                                                        *****  +-----------+  ******
                                                 *******                            *****
                                            *****                                        ******
                                        ****                                                   ***
                          +--------------+                                                    +---------------+
                          | get_cve_data |                                                    | get_cvss_data |
                          +--------------+                                                    +---------------+
                          ..             ...                                                 ...               ...
                       ...                  ...                                           ...                     ..
                     ..                        ..                                       ..                          ...
        +-------------------+                    ...                    +-------------------------+                 .....
        | generate_asd_data |***                    ...                 | get_cvss_statement_data |         ........
        +-------------------+   ********               ....             +-------------------------+ ........
                                        *********          ...               **            .........
                                                 ********     ...         ***      ........
                                                         *****   ..     **    .....
                                                          +---------------------+
                                                          | normalize_cvss_data |
                                                          +---------------------+
                                                                      *
                                                                      *
                                                                      *
                                                          +----------------------+
                                                          | generate_cvss_vector |
                                                          +----------------------+
                                                                      *
                                                                      *
                                                                      *
                                                                +---------+
                                                                | __end__ |
                                                                +---------+
        """
        builder = StateGraph(CVSSVectorState)

        # 添加节点
        builder.add_node("get_cve_data", self._get_cve_data)
        builder.add_node("get_cvss_data", self._get_cvss_data)
        builder.add_node("get_cvss_statement_data", self._get_cvss_statement_data)
        builder.add_node("normalize_cvss_data", self._normalize_cvss_data)
        builder.add_node("generate_asd_data", self._generate_asd_data)
        builder.add_node("generate_cvss_vector", self._generate_cvss_vector)
        builder.add_node("generate_cvss_severity", self._generate_cvss_severity)
        # **TODO**: 后面添加组件画像节点用于增强针对产品差异化的cvss风险评估分析
        # builder.add_node("generate_component_profile")

        # 添加边
        # get_cve_data, get_cvss_data, generate_asd_data 是并行节点用于加速agent执行
        builder.add_edge(START, "get_cve_data")
        # 如果没有cve_data，则直接到normalize_cvss_data节点进行空数据处理；否则到generate_asd_data节点生成ASD数据
        builder.add_conditional_edges(
            "get_cve_data",
            self._is_cve_data_empty,
            ["generate_asd_data", "normalize_cvss_data"],
        )
        builder.add_edge(START, "get_cvss_data")
        # 如果没有cvss_data，则直接到normalize_cvss_data节点进行空数据处理；否则到get_cvss_statement_data节点获取额外CVSS Statement数据
        builder.add_conditional_edges(
            "get_cvss_data",
            self._cvss_data_check,
            ["get_cvss_statement_data", "normalize_cvss_data"],
        )
        # builder.add_edge("get_cve_data", "generate_asd_data")
        builder.add_edge("generate_asd_data", "normalize_cvss_data")
        builder.add_edge("get_cvss_statement_data", "normalize_cvss_data")
        builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
        builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
        builder.add_edge("generate_cvss_severity", END)
        return builder.compile(
            checkpointer=self.memory,
        )

    def _is_cve_data_empty(self, state: CVSSVectorState):
        """检查CVE数据是否为空"""
        if state.get("cve_data") is None:
            return "normalize_cvss_data"
        return "generate_asd_data"

    def _cvss_data_check(self, state: CVSSVectorState):
        """检查CVSS数据是否为空"""
        if state.get("cvss_data") is None:
            return "normalize_cvss_data"
        return self._cvss_vector_check(state)

    def _get_cve_data(self, state: CVSSVectorState):
        """CVE数据Agent节点"""
        self.logger.info("开始获取CVE数据")

        try:
            # 从消息中提取CVE查询内容
            if state["messages"] is None:
                return {
                    "cve_data": None,
                    "error_message": "没有提供CVE查询消息",
                }

            # 获取最后一条用户消息
            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cve_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cve_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"请查询CVE数据: {last_message.content}"])
            )

            # 检查是否有cve_data字段（成功情况）
            if "cve_data" in cve_result and cve_result["cve_data"]:
                cve_data = cve_result["cve_data"]
                self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                return {
                    "cve_data": cve_data,
                    "messages": state["messages"],
                }

            # 如果没有cve_data，尝试从最后一条消息解析
            last_message = cve_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    import json

                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cve_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVE数据: {data.get('error_message')}",
                        }
                    cve_data = CVEData(**data)
                    self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                    return {
                        "cve_data": cve_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cve_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cve_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }

        except Exception as e:
            self.logger.error(f"获取CVE数据时发生错误: {e}")
            return {
                "cve_data": None,
                "error_message": f"获取CVE数据时发生错误: {str(e)}",
            }

    def _get_cvss_data(self, state: CVSSVectorState):
        """CVSS数据Agent节点"""
        self.logger.info("开始获取CVSS数据")
        try:
            if state["messages"] is None:
                return {
                    "cvss_data": None,
                    "error_message": "没有提供CVSS查询消息",
                }

            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cvss_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cvss_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"查询CVE CVSS数据: {last_message.content}"])
            )

            # 检查是否有cvss_data字段（成功情况）
            if "cvss_data" in cvss_result and cvss_result["cvss_data"]:
                cvss_data = cvss_result["cvss_data"]
                self.logger.info(f"成功获取CVSS数据: {cvss_data}")
                return {
                    "cvss_data": cvss_data,
                    "messages": state["messages"],
                }
            # 如果没有cvss_data，尝试从最后一条消息解析
            last_message = cvss_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cvss_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVSS数据: {data.get('error_message')}",
                        }
                    cvss_data = [CVSSData(**item) for item in data]
                    self.logger.info(f"成功获取CVSS数据: {cvss_data}")

                    return {
                        "cvss_data": cvss_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cvss_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cvss_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }
        except Exception as e:
            self.logger.error(f"获取CVSS数据时发生错误: {e}")
            return {
                "cvss_data": None,
                "error_message": f"获取CVSS数据时发生错误: {str(e)}",
            }

    def _cvss_vector_check(self, state: CVSSVectorState):
        """CVSS向量检查节点，检查原始数据中CVSS是否一致"""
        self.logger.info("开始检查CVSS向量")
        try:
            if state.get("cvss_data") is None:
                state["error_message"] = "没有提供CVSS数据"
                return "normalize_cvss_data"

            cvss_data_list = state["cvss_data"]
            cvss_data_len = len(cvss_data_list)
            match cvss_data_len:
                case 0:
                    state["error_message"] = "没有提供CVSS数据"
                    return "normalize_cvss_data"
                case 1:
                    cvss_data = cvss_data_list[0]
                    state["cvss_vector"] = cvss_data.cvssv3_vector
                    return "normalize_cvss_data"
                case _:
                    for cvss in cvss_data_list:
                        if cvss.cve_from == LABEL_REDHAT:
                            return "get_cvss_statement_data"

                    # 如果没有redhat数据，则返回normalize_cvss_data
                    return "normalize_cvss_data"

        except Exception as e:
            self.logger.error(f"检查CVSS向量时发生错误: {e}")
            return "normalize_cvss_data"

    def _get_cvss_statement_data(self, state: CVSSVectorState):
        """获取额外CVSS Statement数据节点"""
        self.logger.info("开始获取额外CVSS Statement数据")
        try:
            if state.get("cve_data") is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "没有提供CVE数据",
                }

            cve_data = state["cve_data"]
            cve_id = cve_data.cve_id
            data = get_redhat_cve_details(cve_id)
            if data is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "无法获取Redhat CVE详情数据",
                }

            # 提取data字段
            redhat_data = data.get("data", {})
            if not isinstance(redhat_data, dict):
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "Redhat CVE详情数据格式错误",
                }
            redhat_statement = redhat_data.get("field_cve_statement_text", {}).get(
                "safe_value", None
            )
            redhat_statement_vector = redhat_data.get(
                "field_cve_cvss3_base_metrics", {}
            ).get("safe_value", None)
            return {
                "statement": redhat_statement,
                "statement_vector": redhat_statement_vector,
            }

        except Exception as e:
            self.logger.error(f"获取额外CVSS Statement数据时发生错误: {e}")
            return {
                # 当无法正确获取statement数据的时候，CVSS Agent分析中允许Statement数据为空
                "statement": "",
                "statement_vector": "",
                "error_message": f"获取额外CVSS Statement数据时发生错误: {str(e)}",
            }

    def _normalize_cvss_data(self, state: CVSSVectorState):
        """归一化CVSS向量数据节点"""
        self.logger.info("开始归一化CVSS向量数据")
        # 确保 asd_data 字段存在，如果 generate_asd_data 节点未执行，则初始化为空字符串
        asd_data = state.get("asd_data", "")
        # 处理数据生成异常的情况
        if state.get("cve_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVE数据",
            }
        if state.get("cvss_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVSS数据",
            }

        try:
            # 尝试从cve analysis分析结果中获取cve风险评估数据
            db: CVECleanDBBase = get_db(settings=get_settings())
            cve_id = state["cve_data"].cve_id
            cvs_analysis_results = sorted(
                db.get_cve_clean_result_by_cve_id(cve_id),
                key=lambda x: x.created_time,
                reverse=True,
            )
            if len(cvs_analysis_results) > 0:
                cvs_analysis_result = cvs_analysis_results[0]
                data = json.loads(cvs_analysis_result.result_data)
                # 从结果中获取cvss数据
                # result_data的格式是：
                # {
                #   "cve_id": "CVE-2025-13601",
                #   "cve_cvssv3_score": "7.7",
                #   "cve_cvssv3_severity": "Moderate",
                #   "cve_cvssv3_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H",
                #   "cve_cvssv3_from": "redhat",
                #   ...
                # }
                return {
                    "cvss_vector": data.get("cve_cvssv3_vector"),
                    "vulab_score": data.get("cve_cvssv3_score"),
                    "vulab_severity": data.get("cve_cvssv3_severity"),
                    "asd_data": asd_data,
                }
            else:
                # 从cve analysis结果中获取数据失败，尝试从cve center数据源中获取
                raise Exception("no cve analysis result found in db")
        except Exception as err:
            self.logger.error(f"没有找到cve analysis结果, error: {err}")
            # 如果cve analysis结果获取失败，则采用VULAB CVSS数据作为风险分析的基准
            for cvss in state["cvss_data"]:
                if cvss.cve_from == LABEL_VULAB:
                    return {
                        "cvss_vector": cvss.cvssv3_vector,
                        "vulab_score": cvss.cvssv3_score,
                        "vulab_severity": cvss.cvssv3_severity,
                        "asd_data": asd_data,
                    }

            # 没有VULAB cvss数据，选择查询到的redhat数据作为基准
            cvss_score_str = _calculate_cvss_score(state.get("statement_vector", ""))
            cvss_score_data = json.loads(cvss_score_str)
            vulab_score = cvss_score_data.get("base_score", 0.0)
            vulab_severity = cvss_score_data.get("base_severity", "Moderate")
            return {
                "cvss_vector": state.get("statement_vector", ""),
                "vulab_score": vulab_score,
                "vulab_severity": vulab_severity,
                "asd_data": asd_data,
                "error_message": "VULAB数据缺失",
            }

    def _generate_asd_data(self, state: CVSSVectorState):
        """生成ASD数据节点"""
        self.logger.info("开始生成ASD数据")
        try:
            cve_data = state["cve_data"]
            asd_data = self.asd_agent.run(cve_data)
            self.logger.info(f"ASD数据: {asd_data.scenario_description[:40]}...")
            return {
                "asd_data": asd_data.scenario_description,
            }
        except Exception as e:
            self.logger.error(f"生成ASD数据时发生错误: {e}")
            return {
                # CVSS分析中允许ASD数据为空，此时需要根据CVE的描述、CVSS向量，结合CVSS 3.1标准对当前的CVSS分析结果进行分析和理解，生成新的CVSS向量
                "asd_data": "",
                "error_message": f"生成ASD数据时发生错误: {str(e)}",
            }

    def _generate_cvss_vector(self, state: CVSSVectorState):
        """生成CVSS向量节点"""
        self.logger.info("开始完成CVE风险评估并生成CVSS向量")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS向量")
            asd_data = state.get("asd_data", "")
            prompt_template = self.prompt_manager.get_cvss_vector_assistant_prompt()
            self.llm.name = "CVSS Vector Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data={
                            "cve_id": cve_data.cve_id,
                            "cve_desc": cve_data.cve_desc,
                        },
                        cvss_vector=state.get("cvss_vector", ""),
                        statement_vector=state.get("statement_vector", ""),
                        statement=state.get("statement", ""),
                        asd_data=asd_data,
                    ),
                ]
            )
            self.logger.info("Generated CVSS Vector Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")

            # 解析响应
            # 自主风险评估结果格式为：
            # {
            #     "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            #     "cvss_severity": "Moderate",
            #     "score": 7.8,
            #     "statement": "CVE-2024-47685的风险差异化评估",
            #     "severity_statement": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            # }
            x_sec_cvss_vector = self._parse_json_response(response.content)
            cvss_score_str = _calculate_cvss_score(
                x_sec_cvss_vector.get("cvss_vector", "")
            )
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 0.0)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS向量时发生错误: {e}")
            cvss_vector = state.get("cvss_vector", "")
            statement = f"{state.get('messages', [])[0].content}的风险评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            return {
                "error_message": f"生成CVSS向量时发生错误: {str(e)}",
                "x_sec_cvss_vector": {
                    "cvss_vector": cvss_vector,
                    "score": 0.0,
                    "cvss_severity": "Moderate",
                    "statement": statement,
                },
            }

    def _generate_cvss_severity(self, state: CVSSVectorState):
        """生成CVSS严重性等级节点"""
        self.logger.info("开始生成CVSS严重性等级")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS严重性等级")
            filter_cve_data = {
                "cve_id": cve_data.cve_id,
                "cve_desc": cve_data.cve_desc,
            }
            x_sec_cvss_vector = state.get("x_sec_cvss_vector")
            if x_sec_cvss_vector is None:
                raise ValueError("CVSS向量数据为空，无法生成CVSS严重性等级")
            prompt_template = self.prompt_manager.get_cvss_severity_assistant_prompt()
            self.llm.name = "CVSS Severity Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data=filter_cve_data,
                        cvss_vector=state.get("x_sec_cvss_vector", {}).get(
                            "cvss_vector", ""
                        ),
                        original_cvss_vector=cve_data.cve_cvssv3_vector,
                        original_cvss_severity=cve_data.cve_cvssv3_severity,
                        asd_data=state.get("asd_data", ""),
                    ),
                ]
            )
            self.logger.info("Generated CVSS Severity Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")
            cvss_severity_data = self._parse_json_response(response.content)
            x_sec_cvss_vector_severity = cvss_severity_data.get(
                "cvss_severity", "Moderate"
            )
            x_sec_cvss_vector_severity_statement = cvss_severity_data.get(
                "severity_statement", ""
            )

            # 更新x_sec_cvss_vector中的严重性等级数据
            x_sec_cvss_vector["cvss_severity"] = x_sec_cvss_vector_severity
            x_sec_cvss_vector["severity_statement"] = (
                x_sec_cvss_vector_severity_statement
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS严重性等级时发生错误: {e}")
            x_sec_cvss_vector = state.get(
                "x_sec_cvss_vector",
                {  # 默认值，当LLM评估严重等级失败时使用
                    "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                    "score": 5.5,
                    "cvss_severity": "Moderate",
                    "severity_statement": "",
                },
            )
            cvss_score_str = _calculate_cvss_score(x_sec_cvss_vector["cvss_vector"])
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 5.5)
            x_sec_cvss_vector["severity_statement"] = (
                f"{x_sec_cvss_vector['cvss_vector']}的严重性等级评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "error_message": f"生成CVSS严重性等级时发生错误: {str(e)}",
            }

    def _parse_json_response(self, content: str):
        """解析JSON响应，支持多种格式"""
        try:
            # 清理响应内容
            content = content.strip()

            # 移除可能的markdown标记
            if content.startswith("```json"):
                content = content[7:]
            if content.endswith("```"):
                content = content[:-3]
            content = content.strip()

            # 尝试解析JSON
            return json.loads(content)

        except json.JSONDecodeError as e:
            self.logger.error(f"Failed to parse JSON response: {e}")
            return {}

    def run(self, message: str):
        """运行CVSS Vector Agent

        Args:
            message: 输入消息
        """
        thread_id = generate_thread_id("cvss-vector-agent")

        result = X_SEC_LLM.invoke(
            name="cvss-vector-agent",
            target=self.agent,
            tags=["cvss", "vector"],
            input=CVSSVectorState(messages=[HumanMessage(content=message)]),
            config={"configurable": {"thread_id": thread_id}},
        )
        # 返回结果格式为：
        # {
        #   "vulab": {
        #     "vulab_cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "vulab_score": 5.5,
        #     "vulab_severity": "Moderate",
        #   },
        #   "mimora": {
        #     "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "score": 5.5,
        #     "cvss_severity": "Moderate",
        #     "severity_statement": "",
        #   },
        #   "default": "vulab" or "mimora",
        # }

        # 制定default策略：
        # 1. 如果两个分数相等，则返回"mimora"
        # 2. 如果两个分数相差超过3分，则返回"vulab"
        # 3. 如果两个分数相差不超过3分，则返回"mimora"
        vulab_score = float(result.get("vulab_score", 0.0))
        mimora_score = float(result.get("x_sec_cvss_vector", {}).get("score", 0.0))
        if vulab_score == mimora_score:
            default = "mimora"
        elif abs(vulab_score - mimora_score) > 3:
            default = "vulab"
        else:
            default = "mimora"
        return {
            "vulab": {
                "cvss_vector": result.get("cvss_vector", ""),
                "score": result.get("vulab_score", 0.0),
                "severity": result.get("vulab_severity", "Moderate"),
            },
            "mimora": result.get("x_sec_cvss_vector", {}),
            "default": default,
        }


# 单例模式，添加全局变量，避免每次创建新的实例
_CVSS_VECTOR_AGENT = CVSSVectorAgent()


# 全局函数，用于CVE CVSS分析，返回JSON字符串
def cve_cvss_analysis(cve_id: str) -> str:
    """CVE CVSS分析"""
    try:
        result = _CVSS_VECTOR_AGENT.run(cve_id)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        _CVSS_VECTOR_AGENT.logger.error(f"CVE CVSS分析失败: {cve_id}, 错误: {str(e)}")
        return json.dumps({}, ensure_ascii=False)


if __name__ == "__main__":
    from internal.config_manager import init_config
    from internal.log import setup_logging

    init_config()
    setup_logging()

    cvss_vector_agent = CVSSVectorAgent()
    print(cvss_vector_agent.agent.get_graph().draw_ascii())
    print(cvss_vector_agent.agent.get_graph().draw_mermaid())
    # benchmark test
    cve_benchmark = [
        "CVE-2024-47685",
        "CVE-2025-40208",
        "CVE-2025-8194",
        "CVE-2025-59375",
        "CVE-2025-10725",
        "CVE-2025-40778",
        "CVE-2025-47277",
        "CVE-2025-5122",
    ]
    cve_benchmark = [
        "CVE-2025-39797",
        "CVE-2025-39833",
        "CVE-2025-21756",
        "CVE-2025-21934",
        "CVE-2025-38590",
        "CVE-2025-40269",
        "CVE-2025-14330",
        "CVE-2025-14321",
    ]
    cve_benchmark = ["cve-2024-47685"]
    result_list = []
    for cve_id in cve_benchmark:
        result = cvss_vector_agent.run(cve_id)
        result_list.append({"cve_id": cve_id, "result": result})

    print("==" * 50)
    print(json.dumps(result_list, indent=2, ensure_ascii=False))
    # mermaid string for agent graph
    graph_mermaid_string = """
    ---
    config:
      flowchart:
        curve: linear
    ---
    graph TD;
        __start__([<p>__start__</p>]):::first
        get_cve_data@{ shape: tag-doc, label: "cve data sub-agent " }
        get_cvss_data@{ shape: tag-doc, label: "cvss data sub-agent " }
        get_cvss_statement_data@{ shape: lin-rect, label: "cvss extra datasub-agent" }
        normalize_cvss_data(normalize_cvss_data)
        generate_asd_data@{ shape: tag-rect, label: "MITRE TTP threat modelsub-agent" }
        generate_cvss_vector(generate_cvss_vector)
        __end__([<p>__end__</p>]):::last
        __start__ --> get_cve_data;
        __start__ --> get_cvss_data;
        generate_asd_data --> normalize_cvss_data;
        get_cve_data -.-> generate_asd_data;
        get_cve_data -.-> normalize_cvss_data;
        get_cvss_data -.-> get_cvss_statement_data;
        get_cvss_data -.-> normalize_cvss_data;
        get_cvss_statement_data --> normalize_cvss_data;
        normalize_cvss_data --> generate_cvss_vector;
        generate_cvss_vector --> __end__;
        classDef default fill:#f2f0ff,line-height:1.2
        classDef first fill-opacity:0
        classDef last fill:#bfb6fc
        style get_cve_data fill:#939,color:#fff
        style get_cvss_data fill:#939,color:#fff
        style generate_asd_data fill:#696,stroke:#333,color:#fff
    """

```

```python
"""
CVSS Vector Agent

CVSS Vector Agent负责根据CVE数据以及受影响产品组件的组件画像信息以及根据TTP模型建立的CVE ASD数据，差异化分析产品的CVE风险并依据CVSS 3.1标准重新生成CVSS向量和打分。
"""

import json
import re
from typing import Optional, List, Annotated

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_community.chat_models import ChatTongyi
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from pydantic import Field, field_validator


def _keep_non_empty_str_reducer(current: str, new: str) -> str:
    """字符串类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字段时，由于并发执行时reducer的调用顺序不确定，
    该函数采用"保留有内容的值"策略：
    - 如果current有内容，保留current
    - 如果current为空但new有内容，使用new
    - 如果都为空，返回空字符串

    这样确保有实际数据的节点产生的值不会被其他节点的空值覆盖。
    """
    # 优先保留有内容的数据
    if current and len(current.strip()) > 0:
        return current
    if new and len(new.strip()) > 0:
        return new
    return ""


def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    """字典类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字典字段时（如 x_sec_cvss_vector），
    该函数采用"合并更新"策略：
    - 以当前值为基础
    - 用新值覆盖/更新已有字段
    - 保留新值中新增的字段

    这样确保并发节点产生的数据可以正确合并，后执行的节点可以更新前面节点设置的值。
    """
    if current is None:
        current = {}
    if new is None:
        new = {}
    # 创建副本避免修改原对象
    result = dict(current)
    # 合并新值，新值覆盖旧值
    result.update(new)
    return result


from core.agents.mimora.tools.cvss_tool import _calculate_cvss_score
from core.agents.mimora.tools.cvss_data_tool import get_redhat_cve_details
from core.agents.mimora.data_type import LABEL_REDHAT, LABEL_VULAB, CVEData, CVSSData
from core.agents.mimora.cve_data_agent import CVEDataAgent, CVEDataState
from core.agents.mimora.mitre_asd_agent import MitreASDAgent
from core.models.llm_manager import X_SEC_LLM
from core.storage.db import CVECleanDBBase
from internal.log import get_logger
from internal.prompt_manager.prompts import CVSSVectorPrompts
from internal.thread_manager import generate_thread_id
from internal.config_manager import get_settings
from server.auth.api_key import get_db


class CVSSVectorState(MessagesState):
    cve_data: CVEData = Field(..., description="cve data")
    cvss_data: List[CVSSData] = Field(
        ..., description="cvss data list from cve source data"
    )
    cvss_vector: str = Field(..., description="cvss vector string")
    vulab_score: float = Field(..., description="vulab cvss score")
    vulab_severity: str = Field(..., description="vulab cvss severity")
    # 使用Annotated配合reducer处理并发更新，避免get_cvss_statement_data和normalize_cvss_data同时写入时的冲突
    statement_vector: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement vector string"
    )
    statement: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_asd_data和normalize_cvss_data同时写入时的冲突
    asd_data: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="asd data string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_cvss_vector和generate_cvss_severity同时写入时的冲突
    x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(
        default_factory=dict, description="x-sec cvss vector object"
    )


class CVSSVectorAgent:
    """CVSS Vector Agent"""

    def __init__(self):
        self.data_agent = CVEDataAgent()
        self.asd_agent = MitreASDAgent()
        self.llm = ChatTongyi(name="cvss-vector-agent-llm", model="qwen3-max")
        self.prompt_manager = CVSSVectorPrompts()
        self.memory = MemorySaver()
        self.agent = self._build_graph()
        self.logger = get_logger()

    def _build_graph(self):
        """构建CVSS Vector Agent的图结构

        工作流程：
        1. 并行获取需要的数据包括：cve data、cvss data、cve asd data
        2. 根据cvss data查看是否需要获取额外CVSS Statement数据
        3. 将上面获取的数据进行归一化
        4. 将归一化后的数据进行CVE风险评估与CVSS向量差异化评估

        图结构如下：
                                                               +-----------+
                                                             **| __start__ |**
                                                        *****  +-----------+  ******
                                                 *******                            *****
                                            *****                                        ******
                                        ****                                                   ***
                          +--------------+                                                    +---------------+
                          | get_cve_data |                                                    | get_cvss_data |
                          +--------------+                                                    +---------------+
                          ..             ...                                                 ...               ...
                       ...                  ...                                           ...                     ..
                     ..                        ..                                       ..                          ...
        +-------------------+                    ...                    +-------------------------+                 .....
        | generate_asd_data |***                    ...                 | get_cvss_statement_data |         ........
        +-------------------+   ********               ....             +-------------------------+ ........
                                        *********          ...               **            .........
                                                 ********     ...         ***      ........
                                                         *****   ..     **    .....
                                                          +---------------------+
                                                          | normalize_cvss_data |
                                                          +---------------------+
                                                                      *
                                                                      *
                                                                      *
                                                          +----------------------+
                                                          | generate_cvss_vector |
                                                          +----------------------+
                                                                      *
                                                                      *
                                                                      *
                                                                +---------+
                                                                | __end__ |
                                                                +---------+
        """
        builder = StateGraph(CVSSVectorState)

        # 添加节点
        builder.add_node("get_cve_data", self._get_cve_data)
        builder.add_node("get_cvss_data", self._get_cvss_data)
        builder.add_node("get_cvss_statement_data", self._get_cvss_statement_data)
        builder.add_node("normalize_cvss_data", self._normalize_cvss_data)
        builder.add_node("generate_asd_data", self._generate_asd_data)
        builder.add_node("generate_cvss_vector", self._generate_cvss_vector)
        builder.add_node("generate_cvss_severity", self._generate_cvss_severity)
        # **TODO**: 后面添加组件画像节点用于增强针对产品差异化的cvss风险评估分析
        # builder.add_node("generate_component_profile")

        # 添加边
        # get_cve_data, get_cvss_data, generate_asd_data 是并行节点用于加速agent执行
        builder.add_edge(START, "get_cve_data")
        # 如果没有cve_data，则直接到normalize_cvss_data节点进行空数据处理；否则到generate_asd_data节点生成ASD数据
        builder.add_conditional_edges(
            "get_cve_data",
            self._is_cve_data_empty,
            ["generate_asd_data", "normalize_cvss_data"],
        )
        builder.add_edge(START, "get_cvss_data")
        # 如果没有cvss_data，则直接到normalize_cvss_data节点进行空数据处理；否则到get_cvss_statement_data节点获取额外CVSS Statement数据
        builder.add_conditional_edges(
            "get_cvss_data",
            self._cvss_data_check,
            ["get_cvss_statement_data", "normalize_cvss_data"],
        )
        # builder.add_edge("get_cve_data", "generate_asd_data")
        builder.add_edge("generate_asd_data", "normalize_cvss_data")
        builder.add_edge("get_cvss_statement_data", "normalize_cvss_data")
        builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
        builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
        builder.add_edge("generate_cvss_severity", END)
        return builder.compile(
            checkpointer=self.memory,
        )

    def _is_cve_data_empty(self, state: CVSSVectorState):
        """检查CVE数据是否为空"""
        if state.get("cve_data") is None:
            return "normalize_cvss_data"
        return "generate_asd_data"

    def _cvss_data_check(self, state: CVSSVectorState):
        """检查CVSS数据是否为空"""
        if state.get("cvss_data") is None:
            return "normalize_cvss_data"
        return self._cvss_vector_check(state)

    def _get_cve_data(self, state: CVSSVectorState):
        """CVE数据Agent节点"""
        self.logger.info("开始获取CVE数据")

        try:
            # 从消息中提取CVE查询内容
            if state["messages"] is None:
                return {
                    "cve_data": None,
                    "error_message": "没有提供CVE查询消息",
                }

            # 获取最后一条用户消息
            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cve_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cve_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"请查询CVE数据: {last_message.content}"])
            )

            # 检查是否有cve_data字段（成功情况）
            if "cve_data" in cve_result and cve_result["cve_data"]:
                cve_data = cve_result["cve_data"]
                self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                return {
                    "cve_data": cve_data,
                    "messages": state["messages"],
                }

            # 如果没有cve_data，尝试从最后一条消息解析
            last_message = cve_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    import json

                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cve_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVE数据: {data.get('error_message')}",
                        }
                    cve_data = CVEData(**data)
                    self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                    return {
                        "cve_data": cve_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cve_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cve_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }

        except Exception as e:
            self.logger.error(f"获取CVE数据时发生错误: {e}")
            return {
                "cve_data": None,
                "error_message": f"获取CVE数据时发生错误: {str(e)}",
            }

    def _get_cvss_data(self, state: CVSSVectorState):
        """CVSS数据Agent节点"""
        self.logger.info("开始获取CVSS数据")
        try:
            if state["messages"] is None:
                return {
                    "cvss_data": None,
                    "error_message": "没有提供CVSS查询消息",
                }

            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cvss_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cvss_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"查询CVE CVSS数据: {last_message.content}"])
            )

            # 检查是否有cvss_data字段（成功情况）
            if "cvss_data" in cvss_result and cvss_result["cvss_data"]:
                cvss_data = cvss_result["cvss_data"]
                self.logger.info(f"成功获取CVSS数据: {cvss_data}")
                return {
                    "cvss_data": cvss_data,
                    "messages": state["messages"],
                }
            # 如果没有cvss_data，尝试从最后一条消息解析
            last_message = cvss_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cvss_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVSS数据: {data.get('error_message')}",
                        }
                    cvss_data = [CVSSData(**item) for item in data]
                    self.logger.info(f"成功获取CVSS数据: {cvss_data}")

                    return {
                        "cvss_data": cvss_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cvss_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cvss_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }
        except Exception as e:
            self.logger.error(f"获取CVSS数据时发生错误: {e}")
            return {
                "cvss_data": None,
                "error_message": f"获取CVSS数据时发生错误: {str(e)}",
            }

    def _cvss_vector_check(self, state: CVSSVectorState):
        """CVSS向量检查节点，检查原始数据中CVSS是否一致"""
        self.logger.info("开始检查CVSS向量")
        try:
            if state.get("cvss_data") is None:
                state["error_message"] = "没有提供CVSS数据"
                return "normalize_cvss_data"

            cvss_data_list = state["cvss_data"]
            cvss_data_len = len(cvss_data_list)
            match cvss_data_len:
                case 0:
                    state["error_message"] = "没有提供CVSS数据"
                    return "normalize_cvss_data"
                case 1:
                    cvss_data = cvss_data_list[0]
                    state["cvss_vector"] = cvss_data.cvssv3_vector
                    return "normalize_cvss_data"
                case _:
                    for cvss in cvss_data_list:
                        if cvss.cve_from == LABEL_REDHAT:
                            return "get_cvss_statement_data"

                    # 如果没有redhat数据，则返回normalize_cvss_data
                    return "normalize_cvss_data"

        except Exception as e:
            self.logger.error(f"检查CVSS向量时发生错误: {e}")
            return "normalize_cvss_data"

    def _get_cvss_statement_data(self, state: CVSSVectorState):
        """获取额外CVSS Statement数据节点"""
        self.logger.info("开始获取额外CVSS Statement数据")
        try:
            if state.get("cve_data") is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "没有提供CVE数据",
                }

            cve_data = state["cve_data"]
            cve_id = cve_data.cve_id
            data = get_redhat_cve_details(cve_id)
            if data is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "无法获取Redhat CVE详情数据",
                }

            # 提取data字段
            redhat_data = data.get("data", {})
            if not isinstance(redhat_data, dict):
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "Redhat CVE详情数据格式错误",
                }
            redhat_statement = redhat_data.get("field_cve_statement_text", {}).get(
                "safe_value", None
            )
            redhat_statement_vector = redhat_data.get(
                "field_cve_cvss3_base_metrics", {}
            ).get("safe_value", None)
            return {
                "statement": redhat_statement,
                "statement_vector": redhat_statement_vector,
            }

        except Exception as e:
            self.logger.error(f"获取额外CVSS Statement数据时发生错误: {e}")
            return {
                # 当无法正确获取statement数据的时候，CVSS Agent分析中允许Statement数据为空
                "statement": "",
                "statement_vector": "",
                "error_message": f"获取额外CVSS Statement数据时发生错误: {str(e)}",
            }

    def _normalize_cvss_data(self, state: CVSSVectorState):
        """归一化CVSS向量数据节点"""
        self.logger.info("开始归一化CVSS向量数据")
        # 确保 asd_data 字段存在，如果 generate_asd_data 节点未执行，则初始化为空字符串
        asd_data = state.get("asd_data", "")
        # 处理数据生成异常的情况
        if state.get("cve_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVE数据",
            }
        if state.get("cvss_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVSS数据",
            }

        try:
            # 尝试从cve analysis分析结果中获取cve风险评估数据
            db: CVECleanDBBase = get_db(settings=get_settings())
            cve_id = state["cve_data"].cve_id
            cvs_analysis_results = sorted(
                db.get_cve_clean_result_by_cve_id(cve_id),
                key=lambda x: x.created_time,
                reverse=True,
            )
            if len(cvs_analysis_results) > 0:
                cvs_analysis_result = cvs_analysis_results[0]
                data = json.loads(cvs_analysis_result.result_data)
                # 从结果中获取cvss数据
                # result_data的格式是：
                # {
                #   "cve_id": "CVE-2025-13601",
                #   "cve_cvssv3_score": "7.7",
                #   "cve_cvssv3_severity": "Moderate",
                #   "cve_cvssv3_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H",
                #   "cve_cvssv3_from": "redhat",
                #   ...
                # }
                return {
                    "cvss_vector": data.get("cve_cvssv3_vector"),
                    "vulab_score": data.get("cve_cvssv3_score"),
                    "vulab_severity": data.get("cve_cvssv3_severity"),
                    "asd_data": asd_data,
                }
            else:
                # 从cve analysis结果中获取数据失败，尝试从cve center数据源中获取
                raise Exception("no cve analysis result found in db")
        except Exception as err:
            self.logger.error(f"没有找到cve analysis结果, error: {err}")
            # 如果cve analysis结果获取失败，则采用VULAB CVSS数据作为风险分析的基准
            for cvss in state["cvss_data"]:
                if cvss.cve_from == LABEL_VULAB:
                    return {
                        "cvss_vector": cvss.cvssv3_vector,
                        "vulab_score": cvss.cvssv3_score,
                        "vulab_severity": cvss.cvssv3_severity,
                        "asd_data": asd_data,
                    }

            # 没有VULAB cvss数据，选择查询到的redhat数据作为基准
            cvss_score_str = _calculate_cvss_score(state.get("statement_vector", ""))
            cvss_score_data = json.loads(cvss_score_str)
            vulab_score = cvss_score_data.get("base_score", 0.0)
            vulab_severity = cvss_score_data.get("base_severity", "Moderate")
            return {
                "cvss_vector": state.get("statement_vector", ""),
                "vulab_score": vulab_score,
                "vulab_severity": vulab_severity,
                "asd_data": asd_data,
                "error_message": "VULAB数据缺失",
            }

    def _generate_asd_data(self, state: CVSSVectorState):
        """生成ASD数据节点"""
        self.logger.info("开始生成ASD数据")
        try:
            cve_data = state["cve_data"]
            asd_data = self.asd_agent.run(cve_data)
            self.logger.info(f"ASD数据: {asd_data.scenario_description[:40]}...")
            return {
                "asd_data": asd_data.scenario_description,
            }
        except Exception as e:
            self.logger.error(f"生成ASD数据时发生错误: {e}")
            return {
                # CVSS分析中允许ASD数据为空，此时需要根据CVE的描述、CVSS向量，结合CVSS 3.1标准对当前的CVSS分析结果进行分析和理解，生成新的CVSS向量
                "asd_data": "",
                "error_message": f"生成ASD数据时发生错误: {str(e)}",
            }

    def _generate_cvss_vector(self, state: CVSSVectorState):
        """生成CVSS向量节点"""
        self.logger.info("开始完成CVE风险评估并生成CVSS向量")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS向量")
            asd_data = state.get("asd_data", "")
            prompt_template = self.prompt_manager.get_cvss_vector_assistant_prompt()
            self.llm.name = "CVSS Vector Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data={
                            "cve_id": cve_data.cve_id,
                            "cve_desc": cve_data.cve_desc,
                        },
                        cvss_vector=state.get("cvss_vector", ""),
                        statement_vector=state.get("statement_vector", ""),
                        statement=state.get("statement", ""),
                        asd_data=asd_data,
                    ),
                ]
            )
            self.logger.info("Generated CVSS Vector Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")

            # 解析响应
            # 自主风险评估结果格式为：
            # {
            #     "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            #     "cvss_severity": "Moderate",
            #     "score": 7.8,
            #     "statement": "CVE-2024-47685的风险差异化评估",
            #     "severity_statement": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            # }
            x_sec_cvss_vector = self._parse_json_response(response.content)
            cvss_score_str = _calculate_cvss_score(
                x_sec_cvss_vector.get("cvss_vector", "")
            )
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 0.0)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS向量时发生错误: {e}")
            cvss_vector = state.get("cvss_vector", "")
            statement = f"{state.get('messages', [])[0].content}的风险评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            return {
                "error_message": f"生成CVSS向量时发生错误: {str(e)}",
                "x_sec_cvss_vector": {
                    "cvss_vector": cvss_vector,
                    "score": 0.0,
                    "cvss_severity": "Moderate",
                    "statement": statement,
                },
            }

    def _generate_cvss_severity(self, state: CVSSVectorState):
        """生成CVSS严重性等级节点"""
        self.logger.info("开始生成CVSS严重性等级")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS严重性等级")
            filter_cve_data = {
                "cve_id": cve_data.cve_id,
                "cve_desc": cve_data.cve_desc,
            }
            x_sec_cvss_vector = state.get("x_sec_cvss_vector")
            if x_sec_cvss_vector is None:
                raise ValueError("CVSS向量数据为空，无法生成CVSS严重性等级")
            prompt_template = self.prompt_manager.get_cvss_severity_assistant_prompt()
            self.llm.name = "CVSS Severity Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data=filter_cve_data,
                        cvss_vector=state.get("x_sec_cvss_vector", {}).get(
                            "cvss_vector", ""
                        ),
                        original_cvss_vector=cve_data.cve_cvssv3_vector,
                        original_cvss_severity=cve_data.cve_cvssv3_severity,
                        asd_data=state.get("asd_data", ""),
                    ),
                ]
            )
            self.logger.info("Generated CVSS Severity Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")
            cvss_severity_data = self._parse_json_response(response.content)
            x_sec_cvss_vector_severity = cvss_severity_data.get(
                "cvss_severity", "Moderate"
            )
            x_sec_cvss_vector_severity_statement = cvss_severity_data.get(
                "severity_statement", ""
            )

            # 更新x_sec_cvss_vector中的严重性等级数据
            x_sec_cvss_vector["cvss_severity"] = x_sec_cvss_vector_severity
            x_sec_cvss_vector["severity_statement"] = (
                x_sec_cvss_vector_severity_statement
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS严重性等级时发生错误: {e}")
            x_sec_cvss_vector = state.get(
                "x_sec_cvss_vector",
                {  # 默认值，当LLM评估严重等级失败时使用
                    "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                    "score": 5.5,
                    "cvss_severity": "Moderate",
                    "severity_statement": "",
                },
            )
            cvss_score_str = _calculate_cvss_score(x_sec_cvss_vector["cvss_vector"])
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 5.5)
            x_sec_cvss_vector["severity_statement"] = (
                f"{x_sec_cvss_vector['cvss_vector']}的严重性等级评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "error_message": f"生成CVSS严重性等级时发生错误: {str(e)}",
            }

    def _parse_json_response(self, content: str):
        """解析JSON响应，支持多种格式"""
        try:
            # 清理响应内容
            content = content.strip()

            # 移除可能的markdown标记
            if content.startswith("```json"):
                content = content[7:]
            if content.endswith("```"):
                content = content[:-3]
            content = content.strip()

            # 尝试解析JSON
            return json.loads(content)

        except json.JSONDecodeError as e:
            self.logger.error(f"Failed to parse JSON response: {e}")
            return {}

    def run(self, message: str):
        """运行CVSS Vector Agent

        Args:
            message: 输入消息
        """
        thread_id = generate_thread_id("cvss-vector-agent")

        result = X_SEC_LLM.invoke(
            name="cvss-vector-agent",
            target=self.agent,
            tags=["cvss", "vector"],
            input=CVSSVectorState(messages=[HumanMessage(content=message)]),
            config={"configurable": {"thread_id": thread_id}},
        )
        # 返回结果格式为：
        # {
        #   "vulab": {
        #     "vulab_cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "vulab_score": 5.5,
        #     "vulab_severity": "Moderate",
        #   },
        #   "mimora": {
        #     "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "score": 5.5,
        #     "cvss_severity": "Moderate",
        #     "severity_statement": "",
        #   },
        #   "default": "vulab" or "mimora",
        # }

        # 制定default策略：
        # 1. 如果两个分数相等，则返回"mimora"
        # 2. 如果两个分数相差超过3分，则返回"vulab"
        # 3. 如果两个分数相差不超过3分，则返回"mimora"
        vulab_score = float(result.get("vulab_score", 0.0))
        mimora_score = float(result.get("x_sec_cvss_vector", {}).get("score", 0.0))
        if vulab_score == mimora_score:
            default = "mimora"
        elif abs(vulab_score - mimora_score) > 3:
            default = "vulab"
        else:
            default = "mimora"
        return {
            "vulab": {
                "cvss_vector": result.get("cvss_vector", ""),
                "score": result.get("vulab_score", 0.0),
                "severity": result.get("vulab_severity", "Moderate"),
            },
            "mimora": result.get("x_sec_cvss_vector", {}),
            "default": default,
        }


# 单例模式，添加全局变量，避免每次创建新的实例
_CVSS_VECTOR_AGENT = CVSSVectorAgent()


# 全局函数，用于CVE CVSS分析，返回JSON字符串
def cve_cvss_analysis(cve_id: str) -> str:
    """CVE CVSS分析"""
    try:
        result = _CVSS_VECTOR_AGENT.run(cve_id)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        _CVSS_VECTOR_AGENT.logger.error(f"CVE CVSS分析失败: {cve_id}, 错误: {str(e)}")
        return json.dumps({}, ensure_ascii=False)


if __name__ == "__main__":
    from internal.config_manager import init_config
    from internal.log import setup_logging

    init_config()
    setup_logging()

    cvss_vector_agent = CVSSVectorAgent()
    print(cvss_vector_agent.agent.get_graph().draw_ascii())
    print(cvss_vector_agent.agent.get_graph().draw_mermaid())
    # benchmark test
    cve_benchmark = [
        "CVE-2024-47685",
        "CVE-2025-40208",
        "CVE-2025-8194",
        "CVE-2025-59375",
        "CVE-2025-10725",
        "CVE-2025-40778",
        "CVE-2025-47277",
        "CVE-2025-5122",
    ]
    cve_benchmark = [
        "CVE-2025-39797",
        "CVE-2025-39833",
        "CVE-2025-21756",
        "CVE-2025-21934",
        "CVE-2025-38590",
        "CVE-2025-40269",
        "CVE-2025-14330",
        "CVE-2025-14321",
    ]
    cve_benchmark = ["cve-2024-47685"]
    result_list = []
    for cve_id in cve_benchmark:
        result = cvss_vector_agent.run(cve_id)
        result_list.append({"cve_id": cve_id, "result": result})

    print("==" * 50)
    print(json.dumps(result_list, indent=2, ensure_ascii=False))
    # mermaid string for agent graph
    graph_mermaid_string = """
    ---
    config:
      flowchart:
        curve: linear
    ---
    graph TD;
        __start__([<p>__start__</p>]):::first
        get_cve_data@{ shape: tag-doc, label: "cve data sub-agent " }
        get_cvss_data@{ shape: tag-doc, label: "cvss data sub-agent " }
        get_cvss_statement_data@{ shape: lin-rect, label: "cvss extra datasub-agent" }
        normalize_cvss_data(normalize_cvss_data)
        generate_asd_data@{ shape: tag-rect, label: "MITRE TTP threat modelsub-agent" }
        generate_cvss_vector(generate_cvss_vector)
        __end__([<p>__end__</p>]):::last
        __start__ --> get_cve_data;
        __start__ --> get_cvss_data;
        generate_asd_data --> normalize_cvss_data;
        get_cve_data -.-> generate_asd_data;
        get_cve_data -.-> normalize_cvss_data;
        get_cvss_data -.-> get_cvss_statement_data;
        get_cvss_data -.-> normalize_cvss_data;
        get_cvss_statement_data --> normalize_cvss_data;
        normalize_cvss_data --> generate_cvss_vector;
        generate_cvss_vector --> __end__;
        classDef default fill:#f2f0ff,line-height:1.2
        classDef first fill-opacity:0
        classDef last fill:#bfb6fc
        style get_cve_data fill:#939,color:#fff
        style get_cvss_data fill:#939,color:#fff
        style generate_asd_data fill:#696,stroke:#333,color:#fff
    """

```

现在让我验证一下修改后的代码：

## 修复完成

问题原因和修复内容如下：

### 问题根因

`x_sec_cvss_vector` 字段存在两个问题：

1. **类型定义错误**：声明为 `str` 类型，但实际存储的是 `dict` 字典对象
2. **缺少并发 reducer**：`generate_cvss_vector` 和 `generate_cvss_severity` 两个节点都会更新这个字段，当图结构中的 `normalize_cvss_data` 被多个入边触发时，会导致下游节点并发执行，从而产生 `INVALID_CONCURRENT_GRAPH_UPDATE` 错误

### 修复内容

1. **新增字典类型 reducer 函数** `_keep_non_empty_dict_reducer`：采用"合并更新"策略，当多个节点并发更新同一字典字段时，会正确合并数据而不是抛出错误

2. **修正 `x_sec_cvss_vector` 类型定义**：
   - 类型从 `str` 改为 `dict`
   - 添加 `Annotated` 配合 reducer 函数处理并发更新
   - 使用 `default_factory=dict` 替代 `...`（必填）

```python
# 修改前
x_sec_cvss_vector: str = Field(..., description="x-sec cvss vector object")

# 修改后
x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(
    default_factory=dict, description="x-sec cvss vector object"
)
```

### 验证建议

可以重新运行对 `CVE-2025-12840` 的分析来验证修复是否生效：

```bash
source .venv/bin/activate && python3 -c "from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis; print(cve_cvss_analysis('CVE-2025-12840'))"
```

```python
# 修改前
x_sec_cvss_vector: str = Field(..., description="x-sec cvss vector object")

# 修改后
x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(
    default_factory=dict, description="x-sec cvss vector object"
)
```

```shellscript
source .venv/bin/activate && python3 -c "from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis; print(cve_cvss_analysis('CVE-2025-12840'))"
```

```python
# 修改前
x_sec_cvss_vector: str = Field(..., description="x-sec cvss vector object")

# 修改后
x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(
    default_factory=dict, description="x-sec cvss vector object"
)
```

```shellscript
source .venv/bin/activate && python3 -c "from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis; print(cve_cvss_analysis('CVE-2025-12840'))"
```

---

**User**

根据langchain stategraph的设计，帮我解释一下当前cvss agent graph的运行逻辑（特别是顺序、并发的情况），我非常好奇为什么normalize_cvss_data被多个入边触发时，下游节点会被多次执行吗？

---

**Cursor**

```python
# 添加边
builder.add_edge(START, "get_cve_data")
builder.add_edge(START, "get_cvss_data")

# get_cve_data 的条件边
builder.add_conditional_edges(
    "get_cve_data",
    self._is_cve_data_empty,
    ["generate_asd_data", "normalize_cvss_data"],
)

# get_cvss_data 的条件边
builder.add_conditional_edges(
    "get_cvss_data",
    self._cvss_data_check,
    ["get_cvss_statement_data", "normalize_cvss_data"],
)

builder.add_edge("generate_asd_data", "normalize_cvss_data")
builder.add_edge("get_cvss_statement_data", "normalize_cvss_data")
builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
builder.add_edge("generate_cvss_severity", END)
```

这是一个非常好的问题！让我详细解释 LangGraph StateGraph 的执行逻辑以及当前 CVSS Vector Agent 图的运行机制。

首先让我查看一下图结构的代码，确保分析准确：

现在我来详细解释 LangGraph StateGraph 的执行逻辑。

## LangGraph StateGraph 执行机制

### 1. 超步执行模型 (Superstep Execution)

LangGraph 使用**超步执行模型**：
- 图按"超步"(superstep)为单位执行
- 每个超步中，所有**可执行**的节点**并行执行**
- 一个节点可执行的条件是：它的所有**活跃前驱**都已完成

### 2. 当前图结构分析

```
                    +--------+
                    | START  |
                    +--------+
                   /          \
                  /            \
         +--------------+    +---------------+
         | get_cve_data |    | get_cvss_data |
         +--------------+    +---------------+
              |                     |
         [条件边]               [条件边]
          /     \                /     \
         ↓       ↓              ↓       ↓
+-------------------+  +--------------------------+
| generate_asd_data |  | get_cvss_statement_data  |
+-------------------+  +--------------------------+
          \                    /
           \                  /
            \                /
             ↓              ↓
         +---------------------+
         | normalize_cvss_data |  ← 多入边汇合点
         +---------------------+
                   ↓
         +----------------------+
         | generate_cvss_vector |
         +----------------------+
                   ↓
         +-----------------------+
         | generate_cvss_severity|
         +-----------------------+
                   ↓
                +-----+
                | END |
                +-----+
```

### 3. 关键问题：多入边的"扇入"(Fan-in)行为

`normalize_cvss_data` 节点有**4个潜在入边**：
1. 来自 `generate_asd_data` 的普通边
2. 来自 `get_cvss_statement_data` 的普通边
3. 来自 `get_cve_data` 的条件边（当 cve_data 为空时）
4. 来自 `get_cvss_data` 的条件边（当 cvss_data 为空时）

**LangGraph 的扇入规则**：

```
当一个节点有多个入边时，LangGraph 会等待当前执行中所有"活跃"的入边完成后，
才执行该节点一次。"活跃"边是指在当前执行路径中实际会被触发的边。
```

### 4. 正常执行流程（典型场景）

假设数据都正常获取到：

```
超步1: START → get_cve_data, get_cvss_data (并行)
超步2: get_cve_data完成 → 条件判断 → generate_asd_data
       get_cvss_data完成 → 条件判断 → get_cvss_statement_data (并行)
超步3: 等待两者都完成 → normalize_cvss_data (执行一次)
超步4: normalize_cvss_data完成 → generate_cvss_vector
超步5: generate_cvss_vector完成 → generate_cvss_severity
超步6: generate_cvss_severity完成 → END
```

### 5. 为什么会出现并发冲突？

从你的日志分析：

```
12:07:16|开始完成CVE风险评估并生成CVSS向量  ← generate_cvss_vector 开始
12:07:16|开始归一化CVSS向量数据              ← normalize_cvss_data 开始
12:07:26|成功计算CVSS评分                    ← generate_cvss_vector 第1次完成
12:07:26|开始完成CVE风险评估并生成CVSS向量  ← generate_cvss_vector 第2次开始!
12:07:26|开始生成CVSS严重性等级              ← generate_cvss_severity 开始
12:07:42|ERROR: x_sec_cvss_vector 并发冲突
```

**问题根源**：`normalize_cvss_data` 被触发了**两次**！

这是因为在 LangGraph 中，当使用条件边时：
- 如果条件边的路径不同步（一条走中间节点，一条直接到目标），可能会导致目标节点被触发多次

具体场景：
```
路径A: get_cve_data → generate_asd_data → normalize_cvss_data
路径B: get_cvss_data → normalize_cvss_data (条件边直接跳转)
```

当路径B的条件边直接指向 `normalize_cvss_data` 时，LangGraph 可能会将其视为一个**独立的触发**，而不是等待路径A完成后合并。

### 6. 状态并发更新问题

当 `normalize_cvss_data` 被触发两次时：

```
第1次触发 → generate_cvss_vector(1) → generate_cvss_severity(1) → 更新 x_sec_cvss_vector
                                                                   ↑
第2次触发 → generate_cvss_vector(2) ─────────────────────────────┘
                                    同时更新！冲突！
```

### 7. reducer 函数的作用

使用 `Annotated` 配合 reducer 函数后：

```python
x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(...)
```

当两个节点同时更新同一个字段时：
- 不再抛出 `INVALID_CONCURRENT_GRAPH_UPDATE` 错误
- 而是调用 reducer 函数合并两个更新

```python
def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    result = dict(current)  # 以当前值为基础
    result.update(new)      # 用新值更新
    return result
```

### 8. 更好的解决方案（可选优化）

如果想要彻底避免 `normalize_cvss_data` 被多次触发，可以考虑重构图结构：

**方案A**：使用显式的 barrier 节点
```python
# 添加一个汇合节点
builder.add_node("wait_for_data", lambda state: state)  # 空操作节点

# 所有数据收集路径都汇合到这个节点
builder.add_edge("generate_asd_data", "wait_for_data")
builder.add_edge("get_cvss_statement_data", "wait_for_data")
# 条件边也指向 wait_for_data 而不是直接指向 normalize_cvss_data
builder.add_edge("wait_for_data", "normalize_cvss_data")
```

**方案B**：移除直接到 `normalize_cvss_data` 的条件边，改为统一走中间节点

当前的 reducer 方案已经可以正确处理并发更新，是一个简洁有效的解决方案。如果需要更严格的顺序控制，可以考虑上述重构方案。

```plaintext
                    +--------+
                    | START  |
                    +--------+
                   /          \
                  /            \
         +--------------+    +---------------+
         | get_cve_data |    | get_cvss_data |
         +--------------+    +---------------+
              |                     |
         [条件边]               [条件边]
          /     \                /     \
         ↓       ↓              ↓       ↓
+-------------------+  +--------------------------+
| generate_asd_data |  | get_cvss_statement_data  |
+-------------------+  +--------------------------+
          \                    /
           \                  /
            \                /
             ↓              ↓
         +---------------------+
         | normalize_cvss_data |  ← 多入边汇合点
         +---------------------+
                   ↓
         +----------------------+
         | generate_cvss_vector |
         +----------------------+
                   ↓
         +-----------------------+
         | generate_cvss_severity|
         +-----------------------+
                   ↓
                +-----+
                | END |
                +-----+
```

```plaintext
当一个节点有多个入边时，LangGraph 会等待当前执行中所有"活跃"的入边完成后，
才执行该节点一次。"活跃"边是指在当前执行路径中实际会被触发的边。
```

```plaintext
超步1: START → get_cve_data, get_cvss_data (并行)
超步2: get_cve_data完成 → 条件判断 → generate_asd_data
       get_cvss_data完成 → 条件判断 → get_cvss_statement_data (并行)
超步3: 等待两者都完成 → normalize_cvss_data (执行一次)
超步4: normalize_cvss_data完成 → generate_cvss_vector
超步5: generate_cvss_vector完成 → generate_cvss_severity
超步6: generate_cvss_severity完成 → END
```

```plaintext
12:07:16|开始完成CVE风险评估并生成CVSS向量  ← generate_cvss_vector 开始
12:07:16|开始归一化CVSS向量数据              ← normalize_cvss_data 开始
12:07:26|成功计算CVSS评分                    ← generate_cvss_vector 第1次完成
12:07:26|开始完成CVE风险评估并生成CVSS向量  ← generate_cvss_vector 第2次开始!
12:07:26|开始生成CVSS严重性等级              ← generate_cvss_severity 开始
12:07:42|ERROR: x_sec_cvss_vector 并发冲突
```

```plaintext
路径A: get_cve_data → generate_asd_data → normalize_cvss_data
路径B: get_cvss_data → normalize_cvss_data (条件边直接跳转)
```

```plaintext
第1次触发 → generate_cvss_vector(1) → generate_cvss_severity(1) → 更新 x_sec_cvss_vector
                                                                   ↑
第2次触发 → generate_cvss_vector(2) ─────────────────────────────┘
                                    同时更新！冲突！
```

```python
x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(...)
```

```python
def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    result = dict(current)  # 以当前值为基础
    result.update(new)      # 用新值更新
    return result
```

```python
# 添加一个汇合节点
builder.add_node("wait_for_data", lambda state: state)  # 空操作节点

# 所有数据收集路径都汇合到这个节点
builder.add_edge("generate_asd_data", "wait_for_data")
builder.add_edge("get_cvss_statement_data", "wait_for_data")
# 条件边也指向 wait_for_data 而不是直接指向 normalize_cvss_data
builder.add_edge("wait_for_data", "normalize_cvss_data")
```

---

**User**

结合langgraph的代码详细解释一下，在 LangGraph 中当使用条件边时：如果条件边的路径不同步（一条走中间节点，一条直接到目标），可能会导致目标节点被触发多次

---

**Cursor**

让我通过查阅 LangGraph 源码来详细解释这个机制。首先让我搜索一下项目中是否有 LangGraph 相关的代码或者我们可以查看 LangGraph 的实现原理。

让我尝试直接访问 LangGraph 的源码：

让我尝试查看 LangGraph 的原始代码文件：

让我查看 `_algo.py`，这是处理任务调度的核心模块：

非常好！现在我可以基于 LangGraph 源码来详细解释这个机制了。

## LangGraph 执行机制深度解析

### 1. 核心概念：通道（Channel）与触发器（Trigger）

LangGraph 的执行不是基于"边"，而是基于**通道（Channel）**和**触发器（Trigger）**：

```python
# 每个节点都有触发器列表，当这些通道被更新时，节点会被触发
class PregelNode:
    triggers: list[str]    # 触发该节点的通道列表
    channels: list[str]    # 该节点读取的通道列表
```

### 2. 节点触发条件

从 `_algo.py` 中的 `_triggers` 函数可以看到触发逻辑：

```python
def _triggers(
    channels: Mapping[str, BaseChannel],
    versions: ChannelVersions,
    seen: ChannelVersions | None,  # 该节点上次看到的版本
    null_version: V,
    proc: PregelNode,
) -> bool:
    """判断节点是否应该被触发"""
    if seen is None:
        # 如果节点从未执行过，只要有任何触发通道可用就触发
        for chan in proc.triggers:
            if channels[chan].is_available():
                return True
    else:
        # 如果节点已执行过，需要检查版本号
        for chan in proc.triggers:
            # 通道可用 且 当前版本 > 上次看到的版本
            if channels[chan].is_available() and versions.get(
                chan, null_version
            ) > seen.get(chan, null_version):
                return True
    return False
```

### 3. 版本追踪机制

在 `apply_writes` 中，每次节点完成后：

```python
def apply_writes(...):
    # 更新节点的 versions_seen
    for task in tasks:
        checkpoint["versions_seen"].setdefault(task.name, {}).update({
            chan: checkpoint["channel_versions"][chan]
            for chan in task.triggers
            if chan in checkpoint["channel_versions"]
        })
    
    # 消费触发通道
    for chan in {chan for task in tasks for chan in task.triggers}:
        if channels[chan].consume() and next_version is not None:
            checkpoint["channel_versions"][chan] = next_version
    
    # 应用写入到通道
    for chan, vals in pending_writes_by_channel.items():
        if channels[chan].update(vals) and next_version is not None:
            checkpoint["channel_versions"][chan] = next_version
```

### 4. 当前 CVSS Vector Agent 的问题分析

让我画出通道流向图：

```
节点名                 触发通道              写入通道
─────────────────────────────────────────────────────────
START              → 写入 "__start__" 通道
                     
get_cve_data       ← 触发于 "__start__"   → 写入 "get_cve_data" 通道
get_cvss_data      ← 触发于 "__start__"   → 写入 "get_cvss_data" 通道

generate_asd_data  ← 触发于 "get_cve_data" → 写入 "generate_asd_data" 通道
                     (条件边选择时)
                     
get_cvss_statement ← 触发于 "get_cvss_data"→ 写入 "get_cvss_statement" 通道  
                     (条件边选择时)

normalize_cvss_data← 触发于 "generate_asd_data" OR "get_cvss_statement" 
                     OR "get_cve_data" OR "get_cvss_data"
                     → 写入 "normalize_cvss_data" 通道
```

### 5. 问题根源：条件边的路径长度不同

当条件边产生**不同长度的路径**时：

```
场景: cve_data 正常, cvss_data 为空

超步1: START
       ├─→ get_cve_data 开始
       └─→ get_cvss_data 开始

超步2: get_cve_data 完成 → 条件返回 "generate_asd_data"
       get_cvss_data 完成 → 条件返回 "normalize_cvss_data" (直接!)
       
       此时有两条活跃路径：
       路径A: get_cve_data → generate_asd_data → normalize_cvss_data (长度=2)
       路径B: get_cvss_data → normalize_cvss_data (长度=1)

超步3: generate_asd_data 开始执行
       normalize_cvss_data 开始执行！(因为 get_cvss_data 已经写入了触发它的通道)
       
       ↑ 这里就出问题了！
```

### 6. 触发多次的根本原因

从源码看，`normalize_cvss_data` 的触发条件是：

```python
# 如果节点订阅的任何触发通道被更新，且版本号高于上次看到的版本，就触发
if channels[chan].is_available() and versions.get(chan) > seen.get(chan):
    return True  # 触发执行
```

**第一次触发**：当 `get_cvss_data` 的条件边直接写入 `normalize_cvss_data` 的触发通道时

**第二次触发**：当 `generate_asd_data` 完成后，写入通道，又触发了 `normalize_cvss_data`

因为 `versions_seen` 是在节点**完成后**更新的，所以第一次执行 `normalize_cvss_data` 时，它还没有标记已经"看过"第一个触发通道，第二个触发通道到来时又会触发一次。

### 7. 时序图示

```
时间 →
─────────────────────────────────────────────────────────────────

超步1: ┌─────────────────┐   ┌─────────────────┐
       │  get_cve_data   │   │  get_cvss_data  │
       └────────┬────────┘   └────────┬────────┘
               ↓                      ↓
超步2:         │                      │
               │ 条件: 走 asd         │ 条件: 直接跳到 normalize
               ↓                      ↓
       ┌─────────────────┐   ┌─────────────────────┐
       │ generate_asd    │   │ normalize_cvss (1次) │ ← 第一次触发!
       └────────┬────────┘   └─────────┬───────────┘
               ↓                       ↓
超步3:         │                       │
               ↓                       ↓
       ┌─────────────────────┐  ┌─────────────────────┐
       │ normalize_cvss (2次)│  │ generate_cvss (1次) │ ← 两个同时进行!
       └─────────┬───────────┘  └─────────┬───────────┘
                 ↓                        ↓
超步4:           ↓                        ↓
       ┌─────────────────────┐  ┌───────────────────────┐
       │ generate_cvss (2次) │  │ generate_severity (1次)│
       └─────────────────────┘  └───────────────────────┘
                 ↘                      ↙
                  ↘                    ↙
                   同时更新 x_sec_cvss_vector
                            ↓
                    ❌ 并发冲突错误！
```

### 8. 为什么 reducer 能解决问题

使用 `Annotated` + reducer 后，LangGraph 不再抛出错误，而是调用 reducer 函数合并更新：

```python
def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    """当两个节点同时更新同一字段时，合并而不是报错"""
    result = dict(current)
    result.update(new)
    return result
```

### 9. 根本解决方案

如果想要完全避免多次触发，需要**统一路径长度**：

```python
# 修改条件边，始终走中间节点
builder.add_conditional_edges(
    "get_cve_data",
    self._is_cve_data_empty,
    ["generate_asd_data"],  # 永远走这个节点，哪怕是空操作
)

builder.add_conditional_edges(
    "get_cvss_data",
    self._cvss_data_check,
    ["get_cvss_statement_data"],  # 永远走这个节点
)
```

或者使用一个**显式的 barrier 节点**来同步所有路径。

这就是 LangGraph 基于通道和版本号的触发机制，以及为什么条件边路径不同步会导致目标节点被多次触发的完整解释。

```python
# 每个节点都有触发器列表，当这些通道被更新时，节点会被触发
class PregelNode:
    triggers: list[str]    # 触发该节点的通道列表
    channels: list[str]    # 该节点读取的通道列表
```

```python
def _triggers(
    channels: Mapping[str, BaseChannel],
    versions: ChannelVersions,
    seen: ChannelVersions | None,  # 该节点上次看到的版本
    null_version: V,
    proc: PregelNode,
) -> bool:
    """判断节点是否应该被触发"""
    if seen is None:
        # 如果节点从未执行过，只要有任何触发通道可用就触发
        for chan in proc.triggers:
            if channels[chan].is_available():
                return True
    else:
        # 如果节点已执行过，需要检查版本号
        for chan in proc.triggers:
            # 通道可用 且 当前版本 > 上次看到的版本
            if channels[chan].is_available() and versions.get(
                chan, null_version
            ) > seen.get(chan, null_version):
                return True
    return False
```

```python
def apply_writes(...):
    # 更新节点的 versions_seen
    for task in tasks:
        checkpoint["versions_seen"].setdefault(task.name, {}).update({
            chan: checkpoint["channel_versions"][chan]
            for chan in task.triggers
            if chan in checkpoint["channel_versions"]
        })
    
    # 消费触发通道
    for chan in {chan for task in tasks for chan in task.triggers}:
        if channels[chan].consume() and next_version is not None:
            checkpoint["channel_versions"][chan] = next_version
    
    # 应用写入到通道
    for chan, vals in pending_writes_by_channel.items():
        if channels[chan].update(vals) and next_version is not None:
            checkpoint["channel_versions"][chan] = next_version
```

```plaintext
节点名                 触发通道              写入通道
─────────────────────────────────────────────────────────
START              → 写入 "__start__" 通道
                     
get_cve_data       ← 触发于 "__start__"   → 写入 "get_cve_data" 通道
get_cvss_data      ← 触发于 "__start__"   → 写入 "get_cvss_data" 通道

generate_asd_data  ← 触发于 "get_cve_data" → 写入 "generate_asd_data" 通道
                     (条件边选择时)
                     
get_cvss_statement ← 触发于 "get_cvss_data"→ 写入 "get_cvss_statement" 通道  
                     (条件边选择时)

normalize_cvss_data← 触发于 "generate_asd_data" OR "get_cvss_statement" 
                     OR "get_cve_data" OR "get_cvss_data"
                     → 写入 "normalize_cvss_data" 通道
```

```plaintext
场景: cve_data 正常, cvss_data 为空

超步1: START
       ├─→ get_cve_data 开始
       └─→ get_cvss_data 开始

超步2: get_cve_data 完成 → 条件返回 "generate_asd_data"
       get_cvss_data 完成 → 条件返回 "normalize_cvss_data" (直接!)
       
       此时有两条活跃路径：
       路径A: get_cve_data → generate_asd_data → normalize_cvss_data (长度=2)
       路径B: get_cvss_data → normalize_cvss_data (长度=1)

超步3: generate_asd_data 开始执行
       normalize_cvss_data 开始执行！(因为 get_cvss_data 已经写入了触发它的通道)
       
       ↑ 这里就出问题了！
```

```python
# 如果节点订阅的任何触发通道被更新，且版本号高于上次看到的版本，就触发
if channels[chan].is_available() and versions.get(chan) > seen.get(chan):
    return True  # 触发执行
```

```plaintext
时间 →
─────────────────────────────────────────────────────────────────

超步1: ┌─────────────────┐   ┌─────────────────┐
       │  get_cve_data   │   │  get_cvss_data  │
       └────────┬────────┘   └────────┬────────┘
               ↓                      ↓
超步2:         │                      │
               │ 条件: 走 asd         │ 条件: 直接跳到 normalize
               ↓                      ↓
       ┌─────────────────┐   ┌─────────────────────┐
       │ generate_asd    │   │ normalize_cvss (1次) │ ← 第一次触发!
       └────────┬────────┘   └─────────┬───────────┘
               ↓                       ↓
超步3:         │                       │
               ↓                       ↓
       ┌─────────────────────┐  ┌─────────────────────┐
       │ normalize_cvss (2次)│  │ generate_cvss (1次) │ ← 两个同时进行!
       └─────────┬───────────┘  └─────────┬───────────┘
                 ↓                        ↓
超步4:           ↓                        ↓
       ┌─────────────────────┐  ┌───────────────────────┐
       │ generate_cvss (2次) │  │ generate_severity (1次)│
       └─────────────────────┘  └───────────────────────┘
                 ↘                      ↙
                  ↘                    ↙
                   同时更新 x_sec_cvss_vector
                            ↓
                    ❌ 并发冲突错误！
```

```python
def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    """当两个节点同时更新同一字段时，合并而不是报错"""
    result = dict(current)
    result.update(new)
    return result
```

```python
# 修改条件边，始终走中间节点
builder.add_conditional_edges(
    "get_cve_data",
    self._is_cve_data_empty,
    ["generate_asd_data"],  # 永远走这个节点，哪怕是空操作
)

builder.add_conditional_edges(
    "get_cvss_data",
    self._cvss_data_check,
    ["get_cvss_statement_data"],  # 永远走这个节点
)
```

---

**User**

什么是显式的 barrier 节点来同步所有路径

---

**Cursor**

## 什么是显式的 Barrier 节点

**Barrier（屏障）节点** 是一种同步机制，用于确保多个并行路径在某一点**汇合**后，再继续执行后续操作。它的作用就像一个"集合点"——所有人到齐了才能一起出发。

### 当前问题的图结构

```
              START
             /      \
            ↓        ↓
     get_cve_data   get_cvss_data
         |              |
    [条件边]        [条件边]
      /    \          /    \
     ↓      ↓        ↓      ↓
  asd_data  ↓   statement  ↓
     \      ↓      /       ↓
      \     ↓     /        ↓
       ↓    ↓    ↓         ↓
        normalize_cvss_data  ← 多个入边，可能被多次触发
              ↓
        generate_cvss_vector
```

### 添加 Barrier 节点后的图结构

```
              START
             /      \
            ↓        ↓
     get_cve_data   get_cvss_data
         |              |
    [条件边]        [条件边]
      /    \          /    \
     ↓      \        /      ↓
  asd_data   \      /   statement
     |        \    /        |
     ↓         \  /         ↓
  asd_barrier   \/    statement_barrier  ← 两个独立的 barrier
     |          /\          |
     ↓         /  \         ↓
      \       /    \       /
       \     /      \     /
        ↓   ↓        ↓   ↓
         sync_barrier        ← 统一的同步点，等所有路径完成
              ↓
        normalize_cvss_data  ← 只会被触发一次！
              ↓
        generate_cvss_vector
```

### 代码实现示例

```python
def _build_graph(self):
    builder = StateGraph(CVSSVectorState)

    # 原有节点
    builder.add_node("get_cve_data", self._get_cve_data)
    builder.add_node("get_cvss_data", self._get_cvss_data)
    builder.add_node("get_cvss_statement_data", self._get_cvss_statement_data)
    builder.add_node("generate_asd_data", self._generate_asd_data)
    builder.add_node("normalize_cvss_data", self._normalize_cvss_data)
    builder.add_node("generate_cvss_vector", self._generate_cvss_vector)
    builder.add_node("generate_cvss_severity", self._generate_cvss_severity)

    # ========== 添加 Barrier 节点 ==========
    
    # 方案1: 单一 sync_barrier 节点（推荐）
    # 空操作节点，只是用来同步所有路径
    def sync_barrier(state: CVSSVectorState):
        """同步屏障节点 - 等待所有前驱完成后再继续"""
        return {}  # 不做任何操作，只是等待
    
    builder.add_node("sync_barrier", sync_barrier)
    
    # ========== 修改边的连接 ==========
    
    # 并行启动
    builder.add_edge(START, "get_cve_data")
    builder.add_edge(START, "get_cvss_data")
    
    # get_cve_data 的条件边 - 始终经过中间节点
    builder.add_conditional_edges(
        "get_cve_data",
        self._is_cve_data_empty,
        {
            "generate_asd_data": "generate_asd_data",
            "skip_asd": "sync_barrier",  # 条件不满足时也走 barrier
        }
    )
    
    # get_cvss_data 的条件边 - 始终经过中间节点
    builder.add_conditional_edges(
        "get_cvss_data",
        self._cvss_data_check,
        {
            "get_cvss_statement_data": "get_cvss_statement_data",
            "skip_statement": "sync_barrier",  # 条件不满足时也走 barrier
        }
    )
    
    # 中间节点都指向 sync_barrier
    builder.add_edge("generate_asd_data", "sync_barrier")
    builder.add_edge("get_cvss_statement_data", "sync_barrier")
    
    # sync_barrier 之后才是 normalize_cvss_data
    builder.add_edge("sync_barrier", "normalize_cvss_data")
    
    # 后续节点保持不变
    builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
    builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
    builder.add_edge("generate_cvss_severity", END)
    
    return builder.compile(checkpointer=self.memory)
```

### Barrier 的工作原理

```
时间 →
─────────────────────────────────────────────────────────────────

超步1: ┌─────────────────┐   ┌─────────────────┐
       │  get_cve_data   │   │  get_cvss_data  │
       └────────┬────────┘   └────────┬────────┘
               ↓                      ↓
超步2:  条件: 走 asd             条件: 跳过 statement
               ↓                      ↓
       ┌─────────────────┐   ┌─────────────────┐
       │ generate_asd    │   │  sync_barrier   │ ← 等待 asd 完成
       └────────┬────────┘   │   (阻塞中...)   │
               ↓             └─────────────────┘
超步3:         ↓                      
       ┌─────────────────┐            
       │  sync_barrier   │ ← 现在所有入边都完成了
       └────────┬────────┘   
               ↓                      
超步4:         ↓             
       ┌─────────────────────┐
       │ normalize_cvss_data │ ← 只执行一次！
       └─────────┬───────────┘
                 ↓
```

### 为什么 Barrier 能解决问题

1. **路径长度统一**：无论条件边选择哪条路径，最终都必须经过 `sync_barrier`
2. **等待所有入边**：LangGraph 会等待 `sync_barrier` 的**所有**入边完成后，才执行它一次
3. **单一出口**：`sync_barrier` 只有一个出边指向 `normalize_cvss_data`，确保下游只触发一次

### 简化版方案

如果不想改动太多代码，可以用更简单的方式 —— 让条件边**始终走中间节点**：

```python
def _is_cve_data_empty(self, state: CVSSVectorState):
    """检查CVE数据是否为空 - 始终返回 generate_asd_data"""
    # 不再直接跳到 normalize_cvss_data
    return "generate_asd_data"

def _generate_asd_data(self, state: CVSSVectorState):
    """生成ASD数据节点 - 处理数据为空的情况"""
    if state.get("cve_data") is None:
        # 数据为空时，返回空的 asd_data，但仍然正常完成
        return {"asd_data": ""}
    
    # 正常生成 ASD 数据
    cve_data = state["cve_data"]
    asd_data = self.asd_agent.run(cve_data)
    return {"asd_data": asd_data.scenario_description}
```

这样就确保了所有路径长度一致，不需要额外的 barrier 节点，而你现在使用的 **reducer 方案**是另一种优雅的解决方式 —— 允许多次触发，但通过合并函数正确处理并发更新。

```plaintext
              START
             /      \
            ↓        ↓
     get_cve_data   get_cvss_data
         |              |
    [条件边]        [条件边]
      /    \          /    \
     ↓      ↓        ↓      ↓
  asd_data  ↓   statement  ↓
     \      ↓      /       ↓
      \     ↓     /        ↓
       ↓    ↓    ↓         ↓
        normalize_cvss_data  ← 多个入边，可能被多次触发
              ↓
        generate_cvss_vector
```

```plaintext
              START
             /      \
            ↓        ↓
     get_cve_data   get_cvss_data
         |              |
    [条件边]        [条件边]
      /    \          /    \
     ↓      \        /      ↓
  asd_data   \      /   statement
     |        \    /        |
     ↓         \  /         ↓
  asd_barrier   \/    statement_barrier  ← 两个独立的 barrier
     |          /\          |
     ↓         /  \         ↓
      \       /    \       /
       \     /      \     /
        ↓   ↓        ↓   ↓
         sync_barrier        ← 统一的同步点，等所有路径完成
              ↓
        normalize_cvss_data  ← 只会被触发一次！
              ↓
        generate_cvss_vector
```

```python
def _build_graph(self):
    builder = StateGraph(CVSSVectorState)

    # 原有节点
    builder.add_node("get_cve_data", self._get_cve_data)
    builder.add_node("get_cvss_data", self._get_cvss_data)
    builder.add_node("get_cvss_statement_data", self._get_cvss_statement_data)
    builder.add_node("generate_asd_data", self._generate_asd_data)
    builder.add_node("normalize_cvss_data", self._normalize_cvss_data)
    builder.add_node("generate_cvss_vector", self._generate_cvss_vector)
    builder.add_node("generate_cvss_severity", self._generate_cvss_severity)

    # ========== 添加 Barrier 节点 ==========
    
    # 方案1: 单一 sync_barrier 节点（推荐）
    # 空操作节点，只是用来同步所有路径
    def sync_barrier(state: CVSSVectorState):
        """同步屏障节点 - 等待所有前驱完成后再继续"""
        return {}  # 不做任何操作，只是等待
    
    builder.add_node("sync_barrier", sync_barrier)
    
    # ========== 修改边的连接 ==========
    
    # 并行启动
    builder.add_edge(START, "get_cve_data")
    builder.add_edge(START, "get_cvss_data")
    
    # get_cve_data 的条件边 - 始终经过中间节点
    builder.add_conditional_edges(
        "get_cve_data",
        self._is_cve_data_empty,
        {
            "generate_asd_data": "generate_asd_data",
            "skip_asd": "sync_barrier",  # 条件不满足时也走 barrier
        }
    )
    
    # get_cvss_data 的条件边 - 始终经过中间节点
    builder.add_conditional_edges(
        "get_cvss_data",
        self._cvss_data_check,
        {
            "get_cvss_statement_data": "get_cvss_statement_data",
            "skip_statement": "sync_barrier",  # 条件不满足时也走 barrier
        }
    )
    
    # 中间节点都指向 sync_barrier
    builder.add_edge("generate_asd_data", "sync_barrier")
    builder.add_edge("get_cvss_statement_data", "sync_barrier")
    
    # sync_barrier 之后才是 normalize_cvss_data
    builder.add_edge("sync_barrier", "normalize_cvss_data")
    
    # 后续节点保持不变
    builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
    builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
    builder.add_edge("generate_cvss_severity", END)
    
    return builder.compile(checkpointer=self.memory)
```

```plaintext
时间 →
─────────────────────────────────────────────────────────────────

超步1: ┌─────────────────┐   ┌─────────────────┐
       │  get_cve_data   │   │  get_cvss_data  │
       └────────┬────────┘   └────────┬────────┘
               ↓                      ↓
超步2:  条件: 走 asd             条件: 跳过 statement
               ↓                      ↓
       ┌─────────────────┐   ┌─────────────────┐
       │ generate_asd    │   │  sync_barrier   │ ← 等待 asd 完成
       └────────┬────────┘   │   (阻塞中...)   │
               ↓             └─────────────────┘
超步3:         ↓                      
       ┌─────────────────┐            
       │  sync_barrier   │ ← 现在所有入边都完成了
       └────────┬────────┘   
               ↓                      
超步4:         ↓             
       ┌─────────────────────┐
       │ normalize_cvss_data │ ← 只执行一次！
       └─────────┬───────────┘
                 ↓
```

```python
def _is_cve_data_empty(self, state: CVSSVectorState):
    """检查CVE数据是否为空 - 始终返回 generate_asd_data"""
    # 不再直接跳到 normalize_cvss_data
    return "generate_asd_data"

def _generate_asd_data(self, state: CVSSVectorState):
    """生成ASD数据节点 - 处理数据为空的情况"""
    if state.get("cve_data") is None:
        # 数据为空时，返回空的 asd_data，但仍然正常完成
        return {"asd_data": ""}
    
    # 正常生成 ASD 数据
    cve_data = state["cve_data"]
    asd_data = self.asd_agent.run(cve_data)
    return {"asd_data": asd_data.scenario_description}
```

---

**User**

使用显式的 barrier 节点来同步所有路径的方案解决cvss agent重复执行节点的问题

---

**Cursor**

我来使用 barrier 节点重构 CVSS Vector Agent 的图结构。首先查看当前的图构建代码：

现在我来实现使用 barrier 节点的方案。我会重构 `_build_graph` 方法和相关的条件判断函数：

```python
"""
CVSS Vector Agent

CVSS Vector Agent负责根据CVE数据以及受影响产品组件的组件画像信息以及根据TTP模型建立的CVE ASD数据，差异化分析产品的CVE风险并依据CVSS 3.1标准重新生成CVSS向量和打分。
"""

import json
import re
from typing import Optional, List, Annotated

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_community.chat_models import ChatTongyi
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from pydantic import Field, field_validator


def _keep_non_empty_str_reducer(current: str, new: str) -> str:
    """字符串类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字段时，由于并发执行时reducer的调用顺序不确定，
    该函数采用"保留有内容的值"策略：
    - 如果current有内容，保留current
    - 如果current为空但new有内容，使用new
    - 如果都为空，返回空字符串

    这样确保有实际数据的节点产生的值不会被其他节点的空值覆盖。
    """
    # 优先保留有内容的数据
    if current and len(current.strip()) > 0:
        return current
    if new and len(new.strip()) > 0:
        return new
    return ""


def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    """字典类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字典字段时（如 x_sec_cvss_vector），
    该函数采用"合并更新"策略：
    - 以当前值为基础
    - 用新值覆盖/更新已有字段
    - 保留新值中新增的字段

    这样确保并发节点产生的数据可以正确合并，后执行的节点可以更新前面节点设置的值。
    """
    if current is None:
        current = {}
    if new is None:
        new = {}
    # 创建副本避免修改原对象
    result = dict(current)
    # 合并新值，新值覆盖旧值
    result.update(new)
    return result


from core.agents.mimora.tools.cvss_tool import _calculate_cvss_score
from core.agents.mimora.tools.cvss_data_tool import get_redhat_cve_details
from core.agents.mimora.data_type import LABEL_REDHAT, LABEL_VULAB, CVEData, CVSSData
from core.agents.mimora.cve_data_agent import CVEDataAgent, CVEDataState
from core.agents.mimora.mitre_asd_agent import MitreASDAgent
from core.models.llm_manager import X_SEC_LLM
from core.storage.db import CVECleanDBBase
from internal.log import get_logger
from internal.prompt_manager.prompts import CVSSVectorPrompts
from internal.thread_manager import generate_thread_id
from internal.config_manager import get_settings
from server.auth.api_key import get_db


class CVSSVectorState(MessagesState):
    cve_data: CVEData = Field(..., description="cve data")
    cvss_data: List[CVSSData] = Field(
        ..., description="cvss data list from cve source data"
    )
    cvss_vector: str = Field(..., description="cvss vector string")
    vulab_score: float = Field(..., description="vulab cvss score")
    vulab_severity: str = Field(..., description="vulab cvss severity")
    # 使用Annotated配合reducer处理并发更新，避免get_cvss_statement_data和normalize_cvss_data同时写入时的冲突
    statement_vector: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement vector string"
    )
    statement: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_asd_data和normalize_cvss_data同时写入时的冲突
    asd_data: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="asd data string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_cvss_vector和generate_cvss_severity同时写入时的冲突
    x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(
        default_factory=dict, description="x-sec cvss vector object"
    )


class CVSSVectorAgent:
    """CVSS Vector Agent"""

    def __init__(self):
        self.data_agent = CVEDataAgent()
        self.asd_agent = MitreASDAgent()
        self.llm = ChatTongyi(name="cvss-vector-agent-llm", model="qwen3-max")
        self.prompt_manager = CVSSVectorPrompts()
        self.memory = MemorySaver()
        self.agent = self._build_graph()
        self.logger = get_logger()

    def _build_graph(self):
        """构建CVSS Vector Agent的图结构

        工作流程：
        1. 并行获取需要的数据包括：cve data、cvss data、cve asd data
        2. 根据cvss data查看是否需要获取额外CVSS Statement数据
        3. 将上面获取的数据进行归一化
        4. 将归一化后的数据进行CVE风险评估与CVSS向量差异化评估

        使用 barrier 节点同步所有路径，确保 normalize_cvss_data 只执行一次。
        新增两个 barrier 节点：
        - cve_path_barrier: 同步 CVE 数据获取路径（无论是否生成 ASD 数据）
        - cvss_path_barrier: 同步 CVSS 数据获取路径（无论是否获取 Statement 数据）

        图结构如下：
                                            +-----------+
                                          **| __start__ |**
                                     *****  +-----------+  ******
                              *******                            *****
                         *****                                        ******
                     ****                                                   ***
       +--------------+                                                    +---------------+
       | get_cve_data |                                                    | get_cvss_data |
       +--------------+                                                    +---------------+
            |                                                                      |
       [条件边]                                                               [条件边]
         /    \\                                                                /    \\
        ↓      ↓                                                              ↓      ↓
  +-----------+  +------------------+                    +------------------+  +------------+
  | asd_data  |  | cve_path_barrier |                    | cvss_path_barrier|  | statement  |
  +-----------+  +------------------+                    +------------------+  +------------+
        \\            /                                            \\            /
         \\          /                                              \\          /
          ↓        ↓                                                ↓        ↓
       +------------------+                                    +-------------------+
       | cve_path_barrier |                                    | cvss_path_barrier |
       +------------------+                                    +-------------------+
                   \\                                                    /
                    \\                                                  /
                     ↓                                                ↓
                      +---------------------------------------------+
                      |            normalize_cvss_data              |
                      +---------------------------------------------+
                                           |
                                           ↓
                      +---------------------------------------------+
                      |            generate_cvss_vector             |
                      +---------------------------------------------+
                                           |
                                           ↓
                      +---------------------------------------------+
                      |            generate_cvss_severity           |
                      +---------------------------------------------+
                                           |
                                           ↓
                                      +---------+
                                      | __end__ |
                                      +---------+
        """
        builder = StateGraph(CVSSVectorState)

        # 添加原有节点
        builder.add_node("get_cve_data", self._get_cve_data)
        builder.add_node("get_cvss_data", self._get_cvss_data)
        builder.add_node("get_cvss_statement_data", self._get_cvss_statement_data)
        builder.add_node("normalize_cvss_data", self._normalize_cvss_data)
        builder.add_node("generate_asd_data", self._generate_asd_data)
        builder.add_node("generate_cvss_vector", self._generate_cvss_vector)
        builder.add_node("generate_cvss_severity", self._generate_cvss_severity)

        # 添加 barrier 节点 - 用于同步路径，确保下游节点只执行一次
        builder.add_node("cve_path_barrier", self._cve_path_barrier)
        builder.add_node("cvss_path_barrier", self._cvss_path_barrier)

        # **TODO**: 后面添加组件画像节点用于增强针对产品差异化的cvss风险评估分析
        # builder.add_node("generate_component_profile")

        # 添加边
        # get_cve_data, get_cvss_data 是并行节点用于加速agent执行
        builder.add_edge(START, "get_cve_data")
        builder.add_edge(START, "get_cvss_data")

        # CVE 数据路径：条件边选择走 generate_asd_data 或直接走 cve_path_barrier
        # 无论哪条路径，最终都会到达 cve_path_barrier，确保路径长度一致
        builder.add_conditional_edges(
            "get_cve_data",
            self._is_cve_data_empty,
            ["generate_asd_data", "cve_path_barrier"],
        )
        # generate_asd_data 完成后也到 cve_path_barrier
        builder.add_edge("generate_asd_data", "cve_path_barrier")

        # CVSS 数据路径：条件边选择走 get_cvss_statement_data 或直接走 cvss_path_barrier
        # 无论哪条路径，最终都会到达 cvss_path_barrier，确保路径长度一致
        builder.add_conditional_edges(
            "get_cvss_data",
            self._cvss_data_check,
            ["get_cvss_statement_data", "cvss_path_barrier"],
        )
        # get_cvss_statement_data 完成后也到 cvss_path_barrier
        builder.add_edge("get_cvss_statement_data", "cvss_path_barrier")

        # 两个 barrier 节点都汇合到 normalize_cvss_data
        # LangGraph 会等待两个 barrier 都完成后才执行 normalize_cvss_data（只执行一次）
        builder.add_edge("cve_path_barrier", "normalize_cvss_data")
        builder.add_edge("cvss_path_barrier", "normalize_cvss_data")

        # 后续节点保持线性执行
        builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
        builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
        builder.add_edge("generate_cvss_severity", END)

        return builder.compile(
            checkpointer=self.memory,
        )

    def _cve_path_barrier(self, state: CVSSVectorState):
        """CVE 数据路径的 barrier 节点

        这是一个同步屏障节点，用于确保 CVE 数据获取路径（无论是否生成 ASD 数据）
        在继续执行前完成同步。不执行任何实际操作，只是作为路径汇合点。
        """
        self.logger.debug("CVE 路径 barrier 节点执行完成")
        return {}

    def _cvss_path_barrier(self, state: CVSSVectorState):
        """CVSS 数据路径的 barrier 节点

        这是一个同步屏障节点，用于确保 CVSS 数据获取路径（无论是否获取 Statement 数据）
        在继续执行前完成同步。不执行任何实际操作，只是作为路径汇合点。
        """
        self.logger.debug("CVSS 路径 barrier 节点执行完成")
        return {}

    def _is_cve_data_empty(self, state: CVSSVectorState):
        """检查CVE数据是否为空"""
        if state.get("cve_data") is None:
            return "normalize_cvss_data"
        return "generate_asd_data"

    def _cvss_data_check(self, state: CVSSVectorState):
        """检查CVSS数据是否为空"""
        if state.get("cvss_data") is None:
            return "normalize_cvss_data"
        return self._cvss_vector_check(state)

    def _get_cve_data(self, state: CVSSVectorState):
        """CVE数据Agent节点"""
        self.logger.info("开始获取CVE数据")

        try:
            # 从消息中提取CVE查询内容
            if state["messages"] is None:
                return {
                    "cve_data": None,
                    "error_message": "没有提供CVE查询消息",
                }

            # 获取最后一条用户消息
            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cve_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cve_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"请查询CVE数据: {last_message.content}"])
            )

            # 检查是否有cve_data字段（成功情况）
            if "cve_data" in cve_result and cve_result["cve_data"]:
                cve_data = cve_result["cve_data"]
                self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                return {
                    "cve_data": cve_data,
                    "messages": state["messages"],
                }

            # 如果没有cve_data，尝试从最后一条消息解析
            last_message = cve_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    import json

                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cve_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVE数据: {data.get('error_message')}",
                        }
                    cve_data = CVEData(**data)
                    self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                    return {
                        "cve_data": cve_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cve_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cve_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }

        except Exception as e:
            self.logger.error(f"获取CVE数据时发生错误: {e}")
            return {
                "cve_data": None,
                "error_message": f"获取CVE数据时发生错误: {str(e)}",
            }

    def _get_cvss_data(self, state: CVSSVectorState):
        """CVSS数据Agent节点"""
        self.logger.info("开始获取CVSS数据")
        try:
            if state["messages"] is None:
                return {
                    "cvss_data": None,
                    "error_message": "没有提供CVSS查询消息",
                }

            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cvss_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cvss_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"查询CVE CVSS数据: {last_message.content}"])
            )

            # 检查是否有cvss_data字段（成功情况）
            if "cvss_data" in cvss_result and cvss_result["cvss_data"]:
                cvss_data = cvss_result["cvss_data"]
                self.logger.info(f"成功获取CVSS数据: {cvss_data}")
                return {
                    "cvss_data": cvss_data,
                    "messages": state["messages"],
                }
            # 如果没有cvss_data，尝试从最后一条消息解析
            last_message = cvss_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cvss_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVSS数据: {data.get('error_message')}",
                        }
                    cvss_data = [CVSSData(**item) for item in data]
                    self.logger.info(f"成功获取CVSS数据: {cvss_data}")

                    return {
                        "cvss_data": cvss_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cvss_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cvss_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }
        except Exception as e:
            self.logger.error(f"获取CVSS数据时发生错误: {e}")
            return {
                "cvss_data": None,
                "error_message": f"获取CVSS数据时发生错误: {str(e)}",
            }

    def _cvss_vector_check(self, state: CVSSVectorState):
        """CVSS向量检查节点，检查原始数据中CVSS是否一致"""
        self.logger.info("开始检查CVSS向量")
        try:
            if state.get("cvss_data") is None:
                state["error_message"] = "没有提供CVSS数据"
                return "normalize_cvss_data"

            cvss_data_list = state["cvss_data"]
            cvss_data_len = len(cvss_data_list)
            match cvss_data_len:
                case 0:
                    state["error_message"] = "没有提供CVSS数据"
                    return "normalize_cvss_data"
                case 1:
                    cvss_data = cvss_data_list[0]
                    state["cvss_vector"] = cvss_data.cvssv3_vector
                    return "normalize_cvss_data"
                case _:
                    for cvss in cvss_data_list:
                        if cvss.cve_from == LABEL_REDHAT:
                            return "get_cvss_statement_data"

                    # 如果没有redhat数据，则返回normalize_cvss_data
                    return "normalize_cvss_data"

        except Exception as e:
            self.logger.error(f"检查CVSS向量时发生错误: {e}")
            return "normalize_cvss_data"

    def _get_cvss_statement_data(self, state: CVSSVectorState):
        """获取额外CVSS Statement数据节点"""
        self.logger.info("开始获取额外CVSS Statement数据")
        try:
            if state.get("cve_data") is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "没有提供CVE数据",
                }

            cve_data = state["cve_data"]
            cve_id = cve_data.cve_id
            data = get_redhat_cve_details(cve_id)
            if data is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "无法获取Redhat CVE详情数据",
                }

            # 提取data字段
            redhat_data = data.get("data", {})
            if not isinstance(redhat_data, dict):
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "Redhat CVE详情数据格式错误",
                }
            redhat_statement = redhat_data.get("field_cve_statement_text", {}).get(
                "safe_value", None
            )
            redhat_statement_vector = redhat_data.get(
                "field_cve_cvss3_base_metrics", {}
            ).get("safe_value", None)
            return {
                "statement": redhat_statement,
                "statement_vector": redhat_statement_vector,
            }

        except Exception as e:
            self.logger.error(f"获取额外CVSS Statement数据时发生错误: {e}")
            return {
                # 当无法正确获取statement数据的时候，CVSS Agent分析中允许Statement数据为空
                "statement": "",
                "statement_vector": "",
                "error_message": f"获取额外CVSS Statement数据时发生错误: {str(e)}",
            }

    def _normalize_cvss_data(self, state: CVSSVectorState):
        """归一化CVSS向量数据节点"""
        self.logger.info("开始归一化CVSS向量数据")
        # 确保 asd_data 字段存在，如果 generate_asd_data 节点未执行，则初始化为空字符串
        asd_data = state.get("asd_data", "")
        # 处理数据生成异常的情况
        if state.get("cve_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVE数据",
            }
        if state.get("cvss_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVSS数据",
            }

        try:
            # 尝试从cve analysis分析结果中获取cve风险评估数据
            db: CVECleanDBBase = get_db(settings=get_settings())
            cve_id = state["cve_data"].cve_id
            cvs_analysis_results = sorted(
                db.get_cve_clean_result_by_cve_id(cve_id),
                key=lambda x: x.created_time,
                reverse=True,
            )
            if len(cvs_analysis_results) > 0:
                cvs_analysis_result = cvs_analysis_results[0]
                data = json.loads(cvs_analysis_result.result_data)
                # 从结果中获取cvss数据
                # result_data的格式是：
                # {
                #   "cve_id": "CVE-2025-13601",
                #   "cve_cvssv3_score": "7.7",
                #   "cve_cvssv3_severity": "Moderate",
                #   "cve_cvssv3_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H",
                #   "cve_cvssv3_from": "redhat",
                #   ...
                # }
                return {
                    "cvss_vector": data.get("cve_cvssv3_vector"),
                    "vulab_score": data.get("cve_cvssv3_score"),
                    "vulab_severity": data.get("cve_cvssv3_severity"),
                    "asd_data": asd_data,
                }
            else:
                # 从cve analysis结果中获取数据失败，尝试从cve center数据源中获取
                raise Exception("no cve analysis result found in db")
        except Exception as err:
            self.logger.warning(f"没有找到cve analysis结果, error: {err}")
            # 如果cve analysis结果获取失败，则采用VULAB CVSS数据作为风险分析的基准
            for cvss in state["cvss_data"]:
                if cvss.cve_from == LABEL_VULAB:
                    return {
                        "cvss_vector": cvss.cvssv3_vector,
                        "vulab_score": cvss.cvssv3_score,
                        "vulab_severity": cvss.cvssv3_severity,
                        "asd_data": asd_data,
                    }

            # 没有VULAB cvss数据，选择查询到的redhat数据作为基准
            cvss_score_str = _calculate_cvss_score(state.get("statement_vector", ""))
            cvss_score_data = json.loads(cvss_score_str)
            vulab_score = cvss_score_data.get("base_score", 0.0)
            vulab_severity = cvss_score_data.get("base_severity", "Moderate")
            return {
                "cvss_vector": state.get("statement_vector", ""),
                "vulab_score": vulab_score,
                "vulab_severity": vulab_severity,
                "asd_data": asd_data,
                "error_message": "VULAB数据缺失",
            }

    def _generate_asd_data(self, state: CVSSVectorState):
        """生成ASD数据节点"""
        self.logger.info("开始生成ASD数据")
        try:
            cve_data = state["cve_data"]
            asd_data = self.asd_agent.run(cve_data)
            self.logger.info(f"ASD数据: {asd_data.scenario_description[:40]}...")
            return {
                "asd_data": asd_data.scenario_description,
            }
        except Exception as e:
            self.logger.error(f"生成ASD数据时发生错误: {e}")
            return {
                # CVSS分析中允许ASD数据为空，此时需要根据CVE的描述、CVSS向量，结合CVSS 3.1标准对当前的CVSS分析结果进行分析和理解，生成新的CVSS向量
                "asd_data": "",
                "error_message": f"生成ASD数据时发生错误: {str(e)}",
            }

    def _generate_cvss_vector(self, state: CVSSVectorState):
        """生成CVSS向量节点"""
        self.logger.info("开始完成CVE风险评估并生成CVSS向量")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS向量")
            asd_data = state.get("asd_data", "")
            prompt_template = self.prompt_manager.get_cvss_vector_assistant_prompt()
            self.llm.name = "CVSS Vector Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data={
                            "cve_id": cve_data.cve_id,
                            "cve_desc": cve_data.cve_desc,
                        },
                        cvss_vector=state.get("cvss_vector", ""),
                        statement_vector=state.get("statement_vector", ""),
                        statement=state.get("statement", ""),
                        asd_data=asd_data,
                    ),
                ]
            )
            self.logger.info("Generated CVSS Vector Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")

            # 解析响应
            # 自主风险评估结果格式为：
            # {
            #     "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            #     "cvss_severity": "Moderate",
            #     "score": 7.8,
            #     "statement": "CVE-2024-47685的风险差异化评估",
            #     "severity_statement": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            # }
            x_sec_cvss_vector = self._parse_json_response(response.content)
            cvss_score_str = _calculate_cvss_score(
                x_sec_cvss_vector.get("cvss_vector", "")
            )
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 0.0)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS向量时发生错误: {e}")
            cvss_vector = state.get("cvss_vector", "")
            statement = f"{state.get('messages', [])[0].content}的风险评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            return {
                "error_message": f"生成CVSS向量时发生错误: {str(e)}",
                "x_sec_cvss_vector": {
                    "cvss_vector": cvss_vector,
                    "score": 0.0,
                    "cvss_severity": "Moderate",
                    "statement": statement,
                },
            }

    def _generate_cvss_severity(self, state: CVSSVectorState):
        """生成CVSS严重性等级节点"""
        self.logger.info("开始生成CVSS严重性等级")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS严重性等级")
            filter_cve_data = {
                "cve_id": cve_data.cve_id,
                "cve_desc": cve_data.cve_desc,
            }
            x_sec_cvss_vector = state.get("x_sec_cvss_vector")
            if x_sec_cvss_vector is None:
                raise ValueError("CVSS向量数据为空，无法生成CVSS严重性等级")
            prompt_template = self.prompt_manager.get_cvss_severity_assistant_prompt()
            self.llm.name = "CVSS Severity Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data=filter_cve_data,
                        cvss_vector=state.get("x_sec_cvss_vector", {}).get(
                            "cvss_vector", ""
                        ),
                        original_cvss_vector=cve_data.cve_cvssv3_vector,
                        original_cvss_severity=cve_data.cve_cvssv3_severity,
                        asd_data=state.get("asd_data", ""),
                    ),
                ]
            )
            self.logger.info("Generated CVSS Severity Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")
            cvss_severity_data = self._parse_json_response(response.content)
            x_sec_cvss_vector_severity = cvss_severity_data.get(
                "cvss_severity", "Moderate"
            )
            x_sec_cvss_vector_severity_statement = cvss_severity_data.get(
                "severity_statement", ""
            )

            # 更新x_sec_cvss_vector中的严重性等级数据
            x_sec_cvss_vector["cvss_severity"] = x_sec_cvss_vector_severity
            x_sec_cvss_vector["severity_statement"] = (
                x_sec_cvss_vector_severity_statement
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS严重性等级时发生错误: {e}")
            x_sec_cvss_vector = state.get(
                "x_sec_cvss_vector",
                {  # 默认值，当LLM评估严重等级失败时使用
                    "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                    "score": 5.5,
                    "cvss_severity": "Moderate",
                    "severity_statement": "",
                },
            )
            cvss_score_str = _calculate_cvss_score(x_sec_cvss_vector["cvss_vector"])
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 5.5)
            x_sec_cvss_vector["severity_statement"] = (
                f"{x_sec_cvss_vector['cvss_vector']}的严重性等级评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "error_message": f"生成CVSS严重性等级时发生错误: {str(e)}",
            }

    def _parse_json_response(self, content: str):
        """解析JSON响应，支持多种格式"""
        try:
            # 清理响应内容
            content = content.strip()

            # 移除可能的markdown标记
            if content.startswith("```json"):
                content = content[7:]
            if content.endswith("```"):
                content = content[:-3]
            content = content.strip()

            # 尝试解析JSON
            return json.loads(content)

        except json.JSONDecodeError as e:
            self.logger.error(f"Failed to parse JSON response: {e}")
            return {}

    def run(self, message: str):
        """运行CVSS Vector Agent

        Args:
            message: 输入消息
        """
        thread_id = generate_thread_id("cvss-vector-agent")

        result = X_SEC_LLM.invoke(
            name="cvss-vector-agent",
            target=self.agent,
            tags=["cvss", "vector"],
            input=CVSSVectorState(messages=[HumanMessage(content=message)]),
            config={"configurable": {"thread_id": thread_id}},
        )
        # 返回结果格式为：
        # {
        #   "vulab": {
        #     "vulab_cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "vulab_score": 5.5,
        #     "vulab_severity": "Moderate",
        #   },
        #   "mimora": {
        #     "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "score": 5.5,
        #     "cvss_severity": "Moderate",
        #     "severity_statement": "",
        #   },
        #   "default": "vulab" or "mimora",
        # }

        # 制定default策略：
        # 1. 如果两个分数相等，则返回"mimora"
        # 2. 如果两个分数相差超过3分，则返回"vulab"
        # 3. 如果两个分数相差不超过3分，则返回"mimora"
        vulab_score = float(result.get("vulab_score", 0.0))
        mimora_score = float(result.get("x_sec_cvss_vector", {}).get("score", 0.0))
        if vulab_score == mimora_score:
            default = "mimora"
        elif abs(vulab_score - mimora_score) > 3:
            default = "vulab"
        else:
            default = "mimora"
        return {
            "vulab": {
                "cvss_vector": result.get("cvss_vector", ""),
                "score": result.get("vulab_score", 0.0),
                "severity": result.get("vulab_severity", "Moderate"),
            },
            "mimora": result.get("x_sec_cvss_vector", {}),
            "default": default,
        }


# 单例模式，添加全局变量，避免每次创建新的实例
_CVSS_VECTOR_AGENT = CVSSVectorAgent()


# 全局函数，用于CVE CVSS分析，返回JSON字符串
def cve_cvss_analysis(cve_id: str) -> str:
    """CVE CVSS分析"""
    try:
        result = _CVSS_VECTOR_AGENT.run(cve_id)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        _CVSS_VECTOR_AGENT.logger.error(f"CVE CVSS分析失败: {cve_id}, 错误: {str(e)}")
        return json.dumps({}, ensure_ascii=False)


if __name__ == "__main__":
    from internal.config_manager import init_config
    from internal.log import setup_logging

    init_config()
    setup_logging()

    cvss_vector_agent = CVSSVectorAgent()
    print(cvss_vector_agent.agent.get_graph().draw_ascii())
    print(cvss_vector_agent.agent.get_graph().draw_mermaid())
    # benchmark test
    cve_benchmark = [
        "CVE-2024-47685",
        "CVE-2025-40208",
        "CVE-2025-8194",
        "CVE-2025-59375",
        "CVE-2025-10725",
        "CVE-2025-40778",
        "CVE-2025-47277",
        "CVE-2025-5122",
    ]
    cve_benchmark = [
        "CVE-2025-39797",
        "CVE-2025-39833",
        "CVE-2025-21756",
        "CVE-2025-21934",
        "CVE-2025-38590",
        "CVE-2025-40269",
        "CVE-2025-14330",
        "CVE-2025-14321",
    ]
    cve_benchmark = ["cve-2024-47685"]
    result_list = []
    for cve_id in cve_benchmark:
        result = cvss_vector_agent.run(cve_id)
        result_list.append({"cve_id": cve_id, "result": result})

    print("==" * 50)
    print(json.dumps(result_list, indent=2, ensure_ascii=False))
    # mermaid string for agent graph
    graph_mermaid_string = """
    ---
    config:
      flowchart:
        curve: linear
    ---
    graph TD;
        __start__([<p>__start__</p>]):::first
        get_cve_data@{ shape: tag-doc, label: "cve data sub-agent " }
        get_cvss_data@{ shape: tag-doc, label: "cvss data sub-agent " }
        get_cvss_statement_data@{ shape: lin-rect, label: "cvss extra datasub-agent" }
        normalize_cvss_data(normalize_cvss_data)
        generate_asd_data@{ shape: tag-rect, label: "MITRE TTP threat modelsub-agent" }
        generate_cvss_vector(generate_cvss_vector)
        __end__([<p>__end__</p>]):::last
        __start__ --> get_cve_data;
        __start__ --> get_cvss_data;
        generate_asd_data --> normalize_cvss_data;
        get_cve_data -.-> generate_asd_data;
        get_cve_data -.-> normalize_cvss_data;
        get_cvss_data -.-> get_cvss_statement_data;
        get_cvss_data -.-> normalize_cvss_data;
        get_cvss_statement_data --> normalize_cvss_data;
        normalize_cvss_data --> generate_cvss_vector;
        generate_cvss_vector --> __end__;
        classDef default fill:#f2f0ff,line-height:1.2
        classDef first fill-opacity:0
        classDef last fill:#bfb6fc
        style get_cve_data fill:#939,color:#fff
        style get_cvss_data fill:#939,color:#fff
        style generate_asd_data fill:#696,stroke:#333,color:#fff
    """

```

现在需要更新条件函数，让它们返回 barrier 节点而不是直接返回 `normalize_cvss_data`：

```python
"""
CVSS Vector Agent

CVSS Vector Agent负责根据CVE数据以及受影响产品组件的组件画像信息以及根据TTP模型建立的CVE ASD数据，差异化分析产品的CVE风险并依据CVSS 3.1标准重新生成CVSS向量和打分。
"""

import json
import re
from typing import Optional, List, Annotated

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_community.chat_models import ChatTongyi
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from pydantic import Field, field_validator


def _keep_non_empty_str_reducer(current: str, new: str) -> str:
    """字符串类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字段时，由于并发执行时reducer的调用顺序不确定，
    该函数采用"保留有内容的值"策略：
    - 如果current有内容，保留current
    - 如果current为空但new有内容，使用new
    - 如果都为空，返回空字符串

    这样确保有实际数据的节点产生的值不会被其他节点的空值覆盖。
    """
    # 优先保留有内容的数据
    if current and len(current.strip()) > 0:
        return current
    if new and len(new.strip()) > 0:
        return new
    return ""


def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    """字典类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字典字段时（如 x_sec_cvss_vector），
    该函数采用"合并更新"策略：
    - 以当前值为基础
    - 用新值覆盖/更新已有字段
    - 保留新值中新增的字段

    这样确保并发节点产生的数据可以正确合并，后执行的节点可以更新前面节点设置的值。
    """
    if current is None:
        current = {}
    if new is None:
        new = {}
    # 创建副本避免修改原对象
    result = dict(current)
    # 合并新值，新值覆盖旧值
    result.update(new)
    return result


from core.agents.mimora.tools.cvss_tool import _calculate_cvss_score
from core.agents.mimora.tools.cvss_data_tool import get_redhat_cve_details
from core.agents.mimora.data_type import LABEL_REDHAT, LABEL_VULAB, CVEData, CVSSData
from core.agents.mimora.cve_data_agent import CVEDataAgent, CVEDataState
from core.agents.mimora.mitre_asd_agent import MitreASDAgent
from core.models.llm_manager import X_SEC_LLM
from core.storage.db import CVECleanDBBase
from internal.log import get_logger
from internal.prompt_manager.prompts import CVSSVectorPrompts
from internal.thread_manager import generate_thread_id
from internal.config_manager import get_settings
from server.auth.api_key import get_db


class CVSSVectorState(MessagesState):
    cve_data: CVEData = Field(..., description="cve data")
    cvss_data: List[CVSSData] = Field(
        ..., description="cvss data list from cve source data"
    )
    cvss_vector: str = Field(..., description="cvss vector string")
    vulab_score: float = Field(..., description="vulab cvss score")
    vulab_severity: str = Field(..., description="vulab cvss severity")
    # 使用Annotated配合reducer处理并发更新，避免get_cvss_statement_data和normalize_cvss_data同时写入时的冲突
    statement_vector: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement vector string"
    )
    statement: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_asd_data和normalize_cvss_data同时写入时的冲突
    asd_data: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="asd data string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_cvss_vector和generate_cvss_severity同时写入时的冲突
    x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(
        default_factory=dict, description="x-sec cvss vector object"
    )


class CVSSVectorAgent:
    """CVSS Vector Agent"""

    def __init__(self):
        self.data_agent = CVEDataAgent()
        self.asd_agent = MitreASDAgent()
        self.llm = ChatTongyi(name="cvss-vector-agent-llm", model="qwen3-max")
        self.prompt_manager = CVSSVectorPrompts()
        self.memory = MemorySaver()
        self.agent = self._build_graph()
        self.logger = get_logger()

    def _build_graph(self):
        """构建CVSS Vector Agent的图结构

        工作流程：
        1. 并行获取需要的数据包括：cve data、cvss data、cve asd data
        2. 根据cvss data查看是否需要获取额外CVSS Statement数据
        3. 将上面获取的数据进行归一化
        4. 将归一化后的数据进行CVE风险评估与CVSS向量差异化评估

        使用 barrier 节点同步所有路径，确保 normalize_cvss_data 只执行一次。
        新增两个 barrier 节点：
        - cve_path_barrier: 同步 CVE 数据获取路径（无论是否生成 ASD 数据）
        - cvss_path_barrier: 同步 CVSS 数据获取路径（无论是否获取 Statement 数据）

        图结构如下：
                                            +-----------+
                                          **| __start__ |**
                                     *****  +-----------+  ******
                              *******                            *****
                         *****                                        ******
                     ****                                                   ***
       +--------------+                                                    +---------------+
       | get_cve_data |                                                    | get_cvss_data |
       +--------------+                                                    +---------------+
            |                                                                      |
       [条件边]                                                               [条件边]
         /    \\                                                                /    \\
        ↓      ↓                                                              ↓      ↓
  +-----------+  +------------------+                    +------------------+  +------------+
  | asd_data  |  | cve_path_barrier |                    | cvss_path_barrier|  | statement  |
  +-----------+  +------------------+                    +------------------+  +------------+
        \\            /                                            \\            /
         \\          /                                              \\          /
          ↓        ↓                                                ↓        ↓
       +------------------+                                    +-------------------+
       | cve_path_barrier |                                    | cvss_path_barrier |
       +------------------+                                    +-------------------+
                   \\                                                    /
                    \\                                                  /
                     ↓                                                ↓
                      +---------------------------------------------+
                      |            normalize_cvss_data              |
                      +---------------------------------------------+
                                           |
                                           ↓
                      +---------------------------------------------+
                      |            generate_cvss_vector             |
                      +---------------------------------------------+
                                           |
                                           ↓
                      +---------------------------------------------+
                      |            generate_cvss_severity           |
                      +---------------------------------------------+
                                           |
                                           ↓
                                      +---------+
                                      | __end__ |
                                      +---------+
        """
        builder = StateGraph(CVSSVectorState)

        # 添加原有节点
        builder.add_node("get_cve_data", self._get_cve_data)
        builder.add_node("get_cvss_data", self._get_cvss_data)
        builder.add_node("get_cvss_statement_data", self._get_cvss_statement_data)
        builder.add_node("normalize_cvss_data", self._normalize_cvss_data)
        builder.add_node("generate_asd_data", self._generate_asd_data)
        builder.add_node("generate_cvss_vector", self._generate_cvss_vector)
        builder.add_node("generate_cvss_severity", self._generate_cvss_severity)

        # 添加 barrier 节点 - 用于同步路径，确保下游节点只执行一次
        builder.add_node("cve_path_barrier", self._cve_path_barrier)
        builder.add_node("cvss_path_barrier", self._cvss_path_barrier)

        # **TODO**: 后面添加组件画像节点用于增强针对产品差异化的cvss风险评估分析
        # builder.add_node("generate_component_profile")

        # 添加边
        # get_cve_data, get_cvss_data 是并行节点用于加速agent执行
        builder.add_edge(START, "get_cve_data")
        builder.add_edge(START, "get_cvss_data")

        # CVE 数据路径：条件边选择走 generate_asd_data 或直接走 cve_path_barrier
        # 无论哪条路径，最终都会到达 cve_path_barrier，确保路径长度一致
        builder.add_conditional_edges(
            "get_cve_data",
            self._is_cve_data_empty,
            ["generate_asd_data", "cve_path_barrier"],
        )
        # generate_asd_data 完成后也到 cve_path_barrier
        builder.add_edge("generate_asd_data", "cve_path_barrier")

        # CVSS 数据路径：条件边选择走 get_cvss_statement_data 或直接走 cvss_path_barrier
        # 无论哪条路径，最终都会到达 cvss_path_barrier，确保路径长度一致
        builder.add_conditional_edges(
            "get_cvss_data",
            self._cvss_data_check,
            ["get_cvss_statement_data", "cvss_path_barrier"],
        )
        # get_cvss_statement_data 完成后也到 cvss_path_barrier
        builder.add_edge("get_cvss_statement_data", "cvss_path_barrier")

        # 两个 barrier 节点都汇合到 normalize_cvss_data
        # LangGraph 会等待两个 barrier 都完成后才执行 normalize_cvss_data（只执行一次）
        builder.add_edge("cve_path_barrier", "normalize_cvss_data")
        builder.add_edge("cvss_path_barrier", "normalize_cvss_data")

        # 后续节点保持线性执行
        builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
        builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
        builder.add_edge("generate_cvss_severity", END)

        return builder.compile(
            checkpointer=self.memory,
        )

    def _cve_path_barrier(self, state: CVSSVectorState):
        """CVE 数据路径的 barrier 节点

        这是一个同步屏障节点，用于确保 CVE 数据获取路径（无论是否生成 ASD 数据）
        在继续执行前完成同步。不执行任何实际操作，只是作为路径汇合点。
        """
        self.logger.debug("CVE 路径 barrier 节点执行完成")
        return {}

    def _cvss_path_barrier(self, state: CVSSVectorState):
        """CVSS 数据路径的 barrier 节点

        这是一个同步屏障节点，用于确保 CVSS 数据获取路径（无论是否获取 Statement 数据）
        在继续执行前完成同步。不执行任何实际操作，只是作为路径汇合点。
        """
        self.logger.debug("CVSS 路径 barrier 节点执行完成")
        return {}

    def _is_cve_data_empty(self, state: CVSSVectorState):
        """检查CVE数据是否为空

        返回值：
        - "generate_asd_data": 如果有 CVE 数据，走 ASD 生成路径
        - "cve_path_barrier": 如果没有 CVE 数据，直接走 barrier 节点
        """
        if state.get("cve_data") is None:
            return "cve_path_barrier"
        return "generate_asd_data"

    def _cvss_data_check(self, state: CVSSVectorState):
        """检查CVSS数据是否为空

        返回值：
        - "get_cvss_statement_data": 如果需要获取额外的 CVSS Statement 数据
        - "cvss_path_barrier": 如果不需要或数据为空，直接走 barrier 节点
        """
        if state.get("cvss_data") is None:
            return "cvss_path_barrier"
        return self._cvss_vector_check(state)

    def _get_cve_data(self, state: CVSSVectorState):
        """CVE数据Agent节点"""
        self.logger.info("开始获取CVE数据")

        try:
            # 从消息中提取CVE查询内容
            if state["messages"] is None:
                return {
                    "cve_data": None,
                    "error_message": "没有提供CVE查询消息",
                }

            # 获取最后一条用户消息
            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cve_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cve_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"请查询CVE数据: {last_message.content}"])
            )

            # 检查是否有cve_data字段（成功情况）
            if "cve_data" in cve_result and cve_result["cve_data"]:
                cve_data = cve_result["cve_data"]
                self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                return {
                    "cve_data": cve_data,
                    "messages": state["messages"],
                }

            # 如果没有cve_data，尝试从最后一条消息解析
            last_message = cve_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    import json

                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cve_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVE数据: {data.get('error_message')}",
                        }
                    cve_data = CVEData(**data)
                    self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                    return {
                        "cve_data": cve_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cve_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cve_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }

        except Exception as e:
            self.logger.error(f"获取CVE数据时发生错误: {e}")
            return {
                "cve_data": None,
                "error_message": f"获取CVE数据时发生错误: {str(e)}",
            }

    def _get_cvss_data(self, state: CVSSVectorState):
        """CVSS数据Agent节点"""
        self.logger.info("开始获取CVSS数据")
        try:
            if state["messages"] is None:
                return {
                    "cvss_data": None,
                    "error_message": "没有提供CVSS查询消息",
                }

            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cvss_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cvss_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"查询CVE CVSS数据: {last_message.content}"])
            )

            # 检查是否有cvss_data字段（成功情况）
            if "cvss_data" in cvss_result and cvss_result["cvss_data"]:
                cvss_data = cvss_result["cvss_data"]
                self.logger.info(f"成功获取CVSS数据: {cvss_data}")
                return {
                    "cvss_data": cvss_data,
                    "messages": state["messages"],
                }
            # 如果没有cvss_data，尝试从最后一条消息解析
            last_message = cvss_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cvss_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVSS数据: {data.get('error_message')}",
                        }
                    cvss_data = [CVSSData(**item) for item in data]
                    self.logger.info(f"成功获取CVSS数据: {cvss_data}")

                    return {
                        "cvss_data": cvss_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cvss_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cvss_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }
        except Exception as e:
            self.logger.error(f"获取CVSS数据时发生错误: {e}")
            return {
                "cvss_data": None,
                "error_message": f"获取CVSS数据时发生错误: {str(e)}",
            }

    def _cvss_vector_check(self, state: CVSSVectorState):
        """CVSS向量检查节点，检查原始数据中CVSS是否一致"""
        self.logger.info("开始检查CVSS向量")
        try:
            if state.get("cvss_data") is None:
                state["error_message"] = "没有提供CVSS数据"
                return "normalize_cvss_data"

            cvss_data_list = state["cvss_data"]
            cvss_data_len = len(cvss_data_list)
            match cvss_data_len:
                case 0:
                    state["error_message"] = "没有提供CVSS数据"
                    return "normalize_cvss_data"
                case 1:
                    cvss_data = cvss_data_list[0]
                    state["cvss_vector"] = cvss_data.cvssv3_vector
                    return "normalize_cvss_data"
                case _:
                    for cvss in cvss_data_list:
                        if cvss.cve_from == LABEL_REDHAT:
                            return "get_cvss_statement_data"

                    # 如果没有redhat数据，则返回normalize_cvss_data
                    return "normalize_cvss_data"

        except Exception as e:
            self.logger.error(f"检查CVSS向量时发生错误: {e}")
            return "normalize_cvss_data"

    def _get_cvss_statement_data(self, state: CVSSVectorState):
        """获取额外CVSS Statement数据节点"""
        self.logger.info("开始获取额外CVSS Statement数据")
        try:
            if state.get("cve_data") is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "没有提供CVE数据",
                }

            cve_data = state["cve_data"]
            cve_id = cve_data.cve_id
            data = get_redhat_cve_details(cve_id)
            if data is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "无法获取Redhat CVE详情数据",
                }

            # 提取data字段
            redhat_data = data.get("data", {})
            if not isinstance(redhat_data, dict):
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "Redhat CVE详情数据格式错误",
                }
            redhat_statement = redhat_data.get("field_cve_statement_text", {}).get(
                "safe_value", None
            )
            redhat_statement_vector = redhat_data.get(
                "field_cve_cvss3_base_metrics", {}
            ).get("safe_value", None)
            return {
                "statement": redhat_statement,
                "statement_vector": redhat_statement_vector,
            }

        except Exception as e:
            self.logger.error(f"获取额外CVSS Statement数据时发生错误: {e}")
            return {
                # 当无法正确获取statement数据的时候，CVSS Agent分析中允许Statement数据为空
                "statement": "",
                "statement_vector": "",
                "error_message": f"获取额外CVSS Statement数据时发生错误: {str(e)}",
            }

    def _normalize_cvss_data(self, state: CVSSVectorState):
        """归一化CVSS向量数据节点"""
        self.logger.info("开始归一化CVSS向量数据")
        # 确保 asd_data 字段存在，如果 generate_asd_data 节点未执行，则初始化为空字符串
        asd_data = state.get("asd_data", "")
        # 处理数据生成异常的情况
        if state.get("cve_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVE数据",
            }
        if state.get("cvss_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVSS数据",
            }

        try:
            # 尝试从cve analysis分析结果中获取cve风险评估数据
            db: CVECleanDBBase = get_db(settings=get_settings())
            cve_id = state["cve_data"].cve_id
            cvs_analysis_results = sorted(
                db.get_cve_clean_result_by_cve_id(cve_id),
                key=lambda x: x.created_time,
                reverse=True,
            )
            if len(cvs_analysis_results) > 0:
                cvs_analysis_result = cvs_analysis_results[0]
                data = json.loads(cvs_analysis_result.result_data)
                # 从结果中获取cvss数据
                # result_data的格式是：
                # {
                #   "cve_id": "CVE-2025-13601",
                #   "cve_cvssv3_score": "7.7",
                #   "cve_cvssv3_severity": "Moderate",
                #   "cve_cvssv3_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H",
                #   "cve_cvssv3_from": "redhat",
                #   ...
                # }
                return {
                    "cvss_vector": data.get("cve_cvssv3_vector"),
                    "vulab_score": data.get("cve_cvssv3_score"),
                    "vulab_severity": data.get("cve_cvssv3_severity"),
                    "asd_data": asd_data,
                }
            else:
                # 从cve analysis结果中获取数据失败，尝试从cve center数据源中获取
                raise Exception("no cve analysis result found in db")
        except Exception as err:
            self.logger.warning(f"没有找到cve analysis结果, error: {err}")
            # 如果cve analysis结果获取失败，则采用VULAB CVSS数据作为风险分析的基准
            for cvss in state["cvss_data"]:
                if cvss.cve_from == LABEL_VULAB:
                    return {
                        "cvss_vector": cvss.cvssv3_vector,
                        "vulab_score": cvss.cvssv3_score,
                        "vulab_severity": cvss.cvssv3_severity,
                        "asd_data": asd_data,
                    }

            # 没有VULAB cvss数据，选择查询到的redhat数据作为基准
            cvss_score_str = _calculate_cvss_score(state.get("statement_vector", ""))
            cvss_score_data = json.loads(cvss_score_str)
            vulab_score = cvss_score_data.get("base_score", 0.0)
            vulab_severity = cvss_score_data.get("base_severity", "Moderate")
            return {
                "cvss_vector": state.get("statement_vector", ""),
                "vulab_score": vulab_score,
                "vulab_severity": vulab_severity,
                "asd_data": asd_data,
                "error_message": "VULAB数据缺失",
            }

    def _generate_asd_data(self, state: CVSSVectorState):
        """生成ASD数据节点"""
        self.logger.info("开始生成ASD数据")
        try:
            cve_data = state["cve_data"]
            asd_data = self.asd_agent.run(cve_data)
            self.logger.info(f"ASD数据: {asd_data.scenario_description[:40]}...")
            return {
                "asd_data": asd_data.scenario_description,
            }
        except Exception as e:
            self.logger.error(f"生成ASD数据时发生错误: {e}")
            return {
                # CVSS分析中允许ASD数据为空，此时需要根据CVE的描述、CVSS向量，结合CVSS 3.1标准对当前的CVSS分析结果进行分析和理解，生成新的CVSS向量
                "asd_data": "",
                "error_message": f"生成ASD数据时发生错误: {str(e)}",
            }

    def _generate_cvss_vector(self, state: CVSSVectorState):
        """生成CVSS向量节点"""
        self.logger.info("开始完成CVE风险评估并生成CVSS向量")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS向量")
            asd_data = state.get("asd_data", "")
            prompt_template = self.prompt_manager.get_cvss_vector_assistant_prompt()
            self.llm.name = "CVSS Vector Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data={
                            "cve_id": cve_data.cve_id,
                            "cve_desc": cve_data.cve_desc,
                        },
                        cvss_vector=state.get("cvss_vector", ""),
                        statement_vector=state.get("statement_vector", ""),
                        statement=state.get("statement", ""),
                        asd_data=asd_data,
                    ),
                ]
            )
            self.logger.info("Generated CVSS Vector Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")

            # 解析响应
            # 自主风险评估结果格式为：
            # {
            #     "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            #     "cvss_severity": "Moderate",
            #     "score": 7.8,
            #     "statement": "CVE-2024-47685的风险差异化评估",
            #     "severity_statement": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            # }
            x_sec_cvss_vector = self._parse_json_response(response.content)
            cvss_score_str = _calculate_cvss_score(
                x_sec_cvss_vector.get("cvss_vector", "")
            )
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 0.0)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS向量时发生错误: {e}")
            cvss_vector = state.get("cvss_vector", "")
            statement = f"{state.get('messages', [])[0].content}的风险评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            return {
                "error_message": f"生成CVSS向量时发生错误: {str(e)}",
                "x_sec_cvss_vector": {
                    "cvss_vector": cvss_vector,
                    "score": 0.0,
                    "cvss_severity": "Moderate",
                    "statement": statement,
                },
            }

    def _generate_cvss_severity(self, state: CVSSVectorState):
        """生成CVSS严重性等级节点"""
        self.logger.info("开始生成CVSS严重性等级")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS严重性等级")
            filter_cve_data = {
                "cve_id": cve_data.cve_id,
                "cve_desc": cve_data.cve_desc,
            }
            x_sec_cvss_vector = state.get("x_sec_cvss_vector")
            if x_sec_cvss_vector is None:
                raise ValueError("CVSS向量数据为空，无法生成CVSS严重性等级")
            prompt_template = self.prompt_manager.get_cvss_severity_assistant_prompt()
            self.llm.name = "CVSS Severity Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data=filter_cve_data,
                        cvss_vector=state.get("x_sec_cvss_vector", {}).get(
                            "cvss_vector", ""
                        ),
                        original_cvss_vector=cve_data.cve_cvssv3_vector,
                        original_cvss_severity=cve_data.cve_cvssv3_severity,
                        asd_data=state.get("asd_data", ""),
                    ),
                ]
            )
            self.logger.info("Generated CVSS Severity Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")
            cvss_severity_data = self._parse_json_response(response.content)
            x_sec_cvss_vector_severity = cvss_severity_data.get(
                "cvss_severity", "Moderate"
            )
            x_sec_cvss_vector_severity_statement = cvss_severity_data.get(
                "severity_statement", ""
            )

            # 更新x_sec_cvss_vector中的严重性等级数据
            x_sec_cvss_vector["cvss_severity"] = x_sec_cvss_vector_severity
            x_sec_cvss_vector["severity_statement"] = (
                x_sec_cvss_vector_severity_statement
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS严重性等级时发生错误: {e}")
            x_sec_cvss_vector = state.get(
                "x_sec_cvss_vector",
                {  # 默认值，当LLM评估严重等级失败时使用
                    "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                    "score": 5.5,
                    "cvss_severity": "Moderate",
                    "severity_statement": "",
                },
            )
            cvss_score_str = _calculate_cvss_score(x_sec_cvss_vector["cvss_vector"])
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 5.5)
            x_sec_cvss_vector["severity_statement"] = (
                f"{x_sec_cvss_vector['cvss_vector']}的严重性等级评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "error_message": f"生成CVSS严重性等级时发生错误: {str(e)}",
            }

    def _parse_json_response(self, content: str):
        """解析JSON响应，支持多种格式"""
        try:
            # 清理响应内容
            content = content.strip()

            # 移除可能的markdown标记
            if content.startswith("```json"):
                content = content[7:]
            if content.endswith("```"):
                content = content[:-3]
            content = content.strip()

            # 尝试解析JSON
            return json.loads(content)

        except json.JSONDecodeError as e:
            self.logger.error(f"Failed to parse JSON response: {e}")
            return {}

    def run(self, message: str):
        """运行CVSS Vector Agent

        Args:
            message: 输入消息
        """
        thread_id = generate_thread_id("cvss-vector-agent")

        result = X_SEC_LLM.invoke(
            name="cvss-vector-agent",
            target=self.agent,
            tags=["cvss", "vector"],
            input=CVSSVectorState(messages=[HumanMessage(content=message)]),
            config={"configurable": {"thread_id": thread_id}},
        )
        # 返回结果格式为：
        # {
        #   "vulab": {
        #     "vulab_cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "vulab_score": 5.5,
        #     "vulab_severity": "Moderate",
        #   },
        #   "mimora": {
        #     "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "score": 5.5,
        #     "cvss_severity": "Moderate",
        #     "severity_statement": "",
        #   },
        #   "default": "vulab" or "mimora",
        # }

        # 制定default策略：
        # 1. 如果两个分数相等，则返回"mimora"
        # 2. 如果两个分数相差超过3分，则返回"vulab"
        # 3. 如果两个分数相差不超过3分，则返回"mimora"
        vulab_score = float(result.get("vulab_score", 0.0))
        mimora_score = float(result.get("x_sec_cvss_vector", {}).get("score", 0.0))
        if vulab_score == mimora_score:
            default = "mimora"
        elif abs(vulab_score - mimora_score) > 3:
            default = "vulab"
        else:
            default = "mimora"
        return {
            "vulab": {
                "cvss_vector": result.get("cvss_vector", ""),
                "score": result.get("vulab_score", 0.0),
                "severity": result.get("vulab_severity", "Moderate"),
            },
            "mimora": result.get("x_sec_cvss_vector", {}),
            "default": default,
        }


# 单例模式，添加全局变量，避免每次创建新的实例
_CVSS_VECTOR_AGENT = CVSSVectorAgent()


# 全局函数，用于CVE CVSS分析，返回JSON字符串
def cve_cvss_analysis(cve_id: str) -> str:
    """CVE CVSS分析"""
    try:
        result = _CVSS_VECTOR_AGENT.run(cve_id)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        _CVSS_VECTOR_AGENT.logger.error(f"CVE CVSS分析失败: {cve_id}, 错误: {str(e)}")
        return json.dumps({}, ensure_ascii=False)


if __name__ == "__main__":
    from internal.config_manager import init_config
    from internal.log import setup_logging

    init_config()
    setup_logging()

    cvss_vector_agent = CVSSVectorAgent()
    print(cvss_vector_agent.agent.get_graph().draw_ascii())
    print(cvss_vector_agent.agent.get_graph().draw_mermaid())
    # benchmark test
    cve_benchmark = [
        "CVE-2024-47685",
        "CVE-2025-40208",
        "CVE-2025-8194",
        "CVE-2025-59375",
        "CVE-2025-10725",
        "CVE-2025-40778",
        "CVE-2025-47277",
        "CVE-2025-5122",
    ]
    cve_benchmark = [
        "CVE-2025-39797",
        "CVE-2025-39833",
        "CVE-2025-21756",
        "CVE-2025-21934",
        "CVE-2025-38590",
        "CVE-2025-40269",
        "CVE-2025-14330",
        "CVE-2025-14321",
    ]
    cve_benchmark = ["cve-2024-47685"]
    result_list = []
    for cve_id in cve_benchmark:
        result = cvss_vector_agent.run(cve_id)
        result_list.append({"cve_id": cve_id, "result": result})

    print("==" * 50)
    print(json.dumps(result_list, indent=2, ensure_ascii=False))
    # mermaid string for agent graph
    graph_mermaid_string = """
    ---
    config:
      flowchart:
        curve: linear
    ---
    graph TD;
        __start__([<p>__start__</p>]):::first
        get_cve_data@{ shape: tag-doc, label: "cve data sub-agent " }
        get_cvss_data@{ shape: tag-doc, label: "cvss data sub-agent " }
        get_cvss_statement_data@{ shape: lin-rect, label: "cvss extra datasub-agent" }
        normalize_cvss_data(normalize_cvss_data)
        generate_asd_data@{ shape: tag-rect, label: "MITRE TTP threat modelsub-agent" }
        generate_cvss_vector(generate_cvss_vector)
        __end__([<p>__end__</p>]):::last
        __start__ --> get_cve_data;
        __start__ --> get_cvss_data;
        generate_asd_data --> normalize_cvss_data;
        get_cve_data -.-> generate_asd_data;
        get_cve_data -.-> normalize_cvss_data;
        get_cvss_data -.-> get_cvss_statement_data;
        get_cvss_data -.-> normalize_cvss_data;
        get_cvss_statement_data --> normalize_cvss_data;
        normalize_cvss_data --> generate_cvss_vector;
        generate_cvss_vector --> __end__;
        classDef default fill:#f2f0ff,line-height:1.2
        classDef first fill-opacity:0
        classDef last fill:#bfb6fc
        style get_cve_data fill:#939,color:#fff
        style get_cvss_data fill:#939,color:#fff
        style generate_asd_data fill:#696,stroke:#333,color:#fff
    """

```

现在需要更新 `_cvss_vector_check` 函数，让它返回 barrier 节点：

```python
"""
CVSS Vector Agent

CVSS Vector Agent负责根据CVE数据以及受影响产品组件的组件画像信息以及根据TTP模型建立的CVE ASD数据，差异化分析产品的CVE风险并依据CVSS 3.1标准重新生成CVSS向量和打分。
"""

import json
import re
from typing import Optional, List, Annotated

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_community.chat_models import ChatTongyi
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from pydantic import Field, field_validator


def _keep_non_empty_str_reducer(current: str, new: str) -> str:
    """字符串类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字段时，由于并发执行时reducer的调用顺序不确定，
    该函数采用"保留有内容的值"策略：
    - 如果current有内容，保留current
    - 如果current为空但new有内容，使用new
    - 如果都为空，返回空字符串

    这样确保有实际数据的节点产生的值不会被其他节点的空值覆盖。
    """
    # 优先保留有内容的数据
    if current and len(current.strip()) > 0:
        return current
    if new and len(new.strip()) > 0:
        return new
    return ""


def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    """字典类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字典字段时（如 x_sec_cvss_vector），
    该函数采用"合并更新"策略：
    - 以当前值为基础
    - 用新值覆盖/更新已有字段
    - 保留新值中新增的字段

    这样确保并发节点产生的数据可以正确合并，后执行的节点可以更新前面节点设置的值。
    """
    if current is None:
        current = {}
    if new is None:
        new = {}
    # 创建副本避免修改原对象
    result = dict(current)
    # 合并新值，新值覆盖旧值
    result.update(new)
    return result


from core.agents.mimora.tools.cvss_tool import _calculate_cvss_score
from core.agents.mimora.tools.cvss_data_tool import get_redhat_cve_details
from core.agents.mimora.data_type import LABEL_REDHAT, LABEL_VULAB, CVEData, CVSSData
from core.agents.mimora.cve_data_agent import CVEDataAgent, CVEDataState
from core.agents.mimora.mitre_asd_agent import MitreASDAgent
from core.models.llm_manager import X_SEC_LLM
from core.storage.db import CVECleanDBBase
from internal.log import get_logger
from internal.prompt_manager.prompts import CVSSVectorPrompts
from internal.thread_manager import generate_thread_id
from internal.config_manager import get_settings
from server.auth.api_key import get_db


class CVSSVectorState(MessagesState):
    cve_data: CVEData = Field(..., description="cve data")
    cvss_data: List[CVSSData] = Field(
        ..., description="cvss data list from cve source data"
    )
    cvss_vector: str = Field(..., description="cvss vector string")
    vulab_score: float = Field(..., description="vulab cvss score")
    vulab_severity: str = Field(..., description="vulab cvss severity")
    # 使用Annotated配合reducer处理并发更新，避免get_cvss_statement_data和normalize_cvss_data同时写入时的冲突
    statement_vector: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement vector string"
    )
    statement: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_asd_data和normalize_cvss_data同时写入时的冲突
    asd_data: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="asd data string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_cvss_vector和generate_cvss_severity同时写入时的冲突
    x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(
        default_factory=dict, description="x-sec cvss vector object"
    )


class CVSSVectorAgent:
    """CVSS Vector Agent"""

    def __init__(self):
        self.data_agent = CVEDataAgent()
        self.asd_agent = MitreASDAgent()
        self.llm = ChatTongyi(name="cvss-vector-agent-llm", model="qwen3-max")
        self.prompt_manager = CVSSVectorPrompts()
        self.memory = MemorySaver()
        self.agent = self._build_graph()
        self.logger = get_logger()

    def _build_graph(self):
        """构建CVSS Vector Agent的图结构

        工作流程：
        1. 并行获取需要的数据包括：cve data、cvss data、cve asd data
        2. 根据cvss data查看是否需要获取额外CVSS Statement数据
        3. 将上面获取的数据进行归一化
        4. 将归一化后的数据进行CVE风险评估与CVSS向量差异化评估

        使用 barrier 节点同步所有路径，确保 normalize_cvss_data 只执行一次。
        新增两个 barrier 节点：
        - cve_path_barrier: 同步 CVE 数据获取路径（无论是否生成 ASD 数据）
        - cvss_path_barrier: 同步 CVSS 数据获取路径（无论是否获取 Statement 数据）

        图结构如下：
                                            +-----------+
                                          **| __start__ |**
                                     *****  +-----------+  ******
                              *******                            *****
                         *****                                        ******
                     ****                                                   ***
       +--------------+                                                    +---------------+
       | get_cve_data |                                                    | get_cvss_data |
       +--------------+                                                    +---------------+
            |                                                                      |
       [条件边]                                                               [条件边]
         /    \\                                                                /    \\
        ↓      ↓                                                              ↓      ↓
  +-----------+  +------------------+                    +------------------+  +------------+
  | asd_data  |  | cve_path_barrier |                    | cvss_path_barrier|  | statement  |
  +-----------+  +------------------+                    +------------------+  +------------+
        \\            /                                            \\            /
         \\          /                                              \\          /
          ↓        ↓                                                ↓        ↓
       +------------------+                                    +-------------------+
       | cve_path_barrier |                                    | cvss_path_barrier |
       +------------------+                                    +-------------------+
                   \\                                                    /
                    \\                                                  /
                     ↓                                                ↓
                      +---------------------------------------------+
                      |            normalize_cvss_data              |
                      +---------------------------------------------+
                                           |
                                           ↓
                      +---------------------------------------------+
                      |            generate_cvss_vector             |
                      +---------------------------------------------+
                                           |
                                           ↓
                      +---------------------------------------------+
                      |            generate_cvss_severity           |
                      +---------------------------------------------+
                                           |
                                           ↓
                                      +---------+
                                      | __end__ |
                                      +---------+
        """
        builder = StateGraph(CVSSVectorState)

        # 添加原有节点
        builder.add_node("get_cve_data", self._get_cve_data)
        builder.add_node("get_cvss_data", self._get_cvss_data)
        builder.add_node("get_cvss_statement_data", self._get_cvss_statement_data)
        builder.add_node("normalize_cvss_data", self._normalize_cvss_data)
        builder.add_node("generate_asd_data", self._generate_asd_data)
        builder.add_node("generate_cvss_vector", self._generate_cvss_vector)
        builder.add_node("generate_cvss_severity", self._generate_cvss_severity)

        # 添加 barrier 节点 - 用于同步路径，确保下游节点只执行一次
        builder.add_node("cve_path_barrier", self._cve_path_barrier)
        builder.add_node("cvss_path_barrier", self._cvss_path_barrier)

        # **TODO**: 后面添加组件画像节点用于增强针对产品差异化的cvss风险评估分析
        # builder.add_node("generate_component_profile")

        # 添加边
        # get_cve_data, get_cvss_data 是并行节点用于加速agent执行
        builder.add_edge(START, "get_cve_data")
        builder.add_edge(START, "get_cvss_data")

        # CVE 数据路径：条件边选择走 generate_asd_data 或直接走 cve_path_barrier
        # 无论哪条路径，最终都会到达 cve_path_barrier，确保路径长度一致
        builder.add_conditional_edges(
            "get_cve_data",
            self._is_cve_data_empty,
            ["generate_asd_data", "cve_path_barrier"],
        )
        # generate_asd_data 完成后也到 cve_path_barrier
        builder.add_edge("generate_asd_data", "cve_path_barrier")

        # CVSS 数据路径：条件边选择走 get_cvss_statement_data 或直接走 cvss_path_barrier
        # 无论哪条路径，最终都会到达 cvss_path_barrier，确保路径长度一致
        builder.add_conditional_edges(
            "get_cvss_data",
            self._cvss_data_check,
            ["get_cvss_statement_data", "cvss_path_barrier"],
        )
        # get_cvss_statement_data 完成后也到 cvss_path_barrier
        builder.add_edge("get_cvss_statement_data", "cvss_path_barrier")

        # 两个 barrier 节点都汇合到 normalize_cvss_data
        # LangGraph 会等待两个 barrier 都完成后才执行 normalize_cvss_data（只执行一次）
        builder.add_edge("cve_path_barrier", "normalize_cvss_data")
        builder.add_edge("cvss_path_barrier", "normalize_cvss_data")

        # 后续节点保持线性执行
        builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
        builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
        builder.add_edge("generate_cvss_severity", END)

        return builder.compile(
            checkpointer=self.memory,
        )

    def _cve_path_barrier(self, state: CVSSVectorState):
        """CVE 数据路径的 barrier 节点

        这是一个同步屏障节点，用于确保 CVE 数据获取路径（无论是否生成 ASD 数据）
        在继续执行前完成同步。不执行任何实际操作，只是作为路径汇合点。
        """
        self.logger.debug("CVE 路径 barrier 节点执行完成")
        return {}

    def _cvss_path_barrier(self, state: CVSSVectorState):
        """CVSS 数据路径的 barrier 节点

        这是一个同步屏障节点，用于确保 CVSS 数据获取路径（无论是否获取 Statement 数据）
        在继续执行前完成同步。不执行任何实际操作，只是作为路径汇合点。
        """
        self.logger.debug("CVSS 路径 barrier 节点执行完成")
        return {}

    def _is_cve_data_empty(self, state: CVSSVectorState):
        """检查CVE数据是否为空

        返回值：
        - "generate_asd_data": 如果有 CVE 数据，走 ASD 生成路径
        - "cve_path_barrier": 如果没有 CVE 数据，直接走 barrier 节点
        """
        if state.get("cve_data") is None:
            return "cve_path_barrier"
        return "generate_asd_data"

    def _cvss_data_check(self, state: CVSSVectorState):
        """检查CVSS数据是否为空

        返回值：
        - "get_cvss_statement_data": 如果需要获取额外的 CVSS Statement 数据
        - "cvss_path_barrier": 如果不需要或数据为空，直接走 barrier 节点
        """
        if state.get("cvss_data") is None:
            return "cvss_path_barrier"
        return self._cvss_vector_check(state)

    def _get_cve_data(self, state: CVSSVectorState):
        """CVE数据Agent节点"""
        self.logger.info("开始获取CVE数据")

        try:
            # 从消息中提取CVE查询内容
            if state["messages"] is None:
                return {
                    "cve_data": None,
                    "error_message": "没有提供CVE查询消息",
                }

            # 获取最后一条用户消息
            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cve_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cve_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"请查询CVE数据: {last_message.content}"])
            )

            # 检查是否有cve_data字段（成功情况）
            if "cve_data" in cve_result and cve_result["cve_data"]:
                cve_data = cve_result["cve_data"]
                self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                return {
                    "cve_data": cve_data,
                    "messages": state["messages"],
                }

            # 如果没有cve_data，尝试从最后一条消息解析
            last_message = cve_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    import json

                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cve_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVE数据: {data.get('error_message')}",
                        }
                    cve_data = CVEData(**data)
                    self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                    return {
                        "cve_data": cve_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cve_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cve_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }

        except Exception as e:
            self.logger.error(f"获取CVE数据时发生错误: {e}")
            return {
                "cve_data": None,
                "error_message": f"获取CVE数据时发生错误: {str(e)}",
            }

    def _get_cvss_data(self, state: CVSSVectorState):
        """CVSS数据Agent节点"""
        self.logger.info("开始获取CVSS数据")
        try:
            if state["messages"] is None:
                return {
                    "cvss_data": None,
                    "error_message": "没有提供CVSS查询消息",
                }

            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cvss_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cvss_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"查询CVE CVSS数据: {last_message.content}"])
            )

            # 检查是否有cvss_data字段（成功情况）
            if "cvss_data" in cvss_result and cvss_result["cvss_data"]:
                cvss_data = cvss_result["cvss_data"]
                self.logger.info(f"成功获取CVSS数据: {cvss_data}")
                return {
                    "cvss_data": cvss_data,
                    "messages": state["messages"],
                }
            # 如果没有cvss_data，尝试从最后一条消息解析
            last_message = cvss_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cvss_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVSS数据: {data.get('error_message')}",
                        }
                    cvss_data = [CVSSData(**item) for item in data]
                    self.logger.info(f"成功获取CVSS数据: {cvss_data}")

                    return {
                        "cvss_data": cvss_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cvss_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cvss_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }
        except Exception as e:
            self.logger.error(f"获取CVSS数据时发生错误: {e}")
            return {
                "cvss_data": None,
                "error_message": f"获取CVSS数据时发生错误: {str(e)}",
            }

    def _cvss_vector_check(self, state: CVSSVectorState):
        """CVSS向量检查节点，检查原始数据中CVSS是否一致

        返回值：
        - "get_cvss_statement_data": 如果需要获取额外的 CVSS Statement 数据（有 RedHat 数据源）
        - "cvss_path_barrier": 其他情况直接走 barrier 节点
        """
        self.logger.info("开始检查CVSS向量")
        try:
            if state.get("cvss_data") is None:
                state["error_message"] = "没有提供CVSS数据"
                return "cvss_path_barrier"

            cvss_data_list = state["cvss_data"]
            cvss_data_len = len(cvss_data_list)
            match cvss_data_len:
                case 0:
                    state["error_message"] = "没有提供CVSS数据"
                    return "cvss_path_barrier"
                case 1:
                    cvss_data = cvss_data_list[0]
                    state["cvss_vector"] = cvss_data.cvssv3_vector
                    return "cvss_path_barrier"
                case _:
                    for cvss in cvss_data_list:
                        if cvss.cve_from == LABEL_REDHAT:
                            return "get_cvss_statement_data"

                    # 如果没有redhat数据，则返回cvss_path_barrier
                    return "cvss_path_barrier"

        except Exception as e:
            self.logger.error(f"检查CVSS向量时发生错误: {e}")
            return "cvss_path_barrier"

    def _get_cvss_statement_data(self, state: CVSSVectorState):
        """获取额外CVSS Statement数据节点"""
        self.logger.info("开始获取额外CVSS Statement数据")
        try:
            if state.get("cve_data") is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "没有提供CVE数据",
                }

            cve_data = state["cve_data"]
            cve_id = cve_data.cve_id
            data = get_redhat_cve_details(cve_id)
            if data is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "无法获取Redhat CVE详情数据",
                }

            # 提取data字段
            redhat_data = data.get("data", {})
            if not isinstance(redhat_data, dict):
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "Redhat CVE详情数据格式错误",
                }
            redhat_statement = redhat_data.get("field_cve_statement_text", {}).get(
                "safe_value", None
            )
            redhat_statement_vector = redhat_data.get(
                "field_cve_cvss3_base_metrics", {}
            ).get("safe_value", None)
            return {
                "statement": redhat_statement,
                "statement_vector": redhat_statement_vector,
            }

        except Exception as e:
            self.logger.error(f"获取额外CVSS Statement数据时发生错误: {e}")
            return {
                # 当无法正确获取statement数据的时候，CVSS Agent分析中允许Statement数据为空
                "statement": "",
                "statement_vector": "",
                "error_message": f"获取额外CVSS Statement数据时发生错误: {str(e)}",
            }

    def _normalize_cvss_data(self, state: CVSSVectorState):
        """归一化CVSS向量数据节点"""
        self.logger.info("开始归一化CVSS向量数据")
        # 确保 asd_data 字段存在，如果 generate_asd_data 节点未执行，则初始化为空字符串
        asd_data = state.get("asd_data", "")
        # 处理数据生成异常的情况
        if state.get("cve_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVE数据",
            }
        if state.get("cvss_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVSS数据",
            }

        try:
            # 尝试从cve analysis分析结果中获取cve风险评估数据
            db: CVECleanDBBase = get_db(settings=get_settings())
            cve_id = state["cve_data"].cve_id
            cvs_analysis_results = sorted(
                db.get_cve_clean_result_by_cve_id(cve_id),
                key=lambda x: x.created_time,
                reverse=True,
            )
            if len(cvs_analysis_results) > 0:
                cvs_analysis_result = cvs_analysis_results[0]
                data = json.loads(cvs_analysis_result.result_data)
                # 从结果中获取cvss数据
                # result_data的格式是：
                # {
                #   "cve_id": "CVE-2025-13601",
                #   "cve_cvssv3_score": "7.7",
                #   "cve_cvssv3_severity": "Moderate",
                #   "cve_cvssv3_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H",
                #   "cve_cvssv3_from": "redhat",
                #   ...
                # }
                return {
                    "cvss_vector": data.get("cve_cvssv3_vector"),
                    "vulab_score": data.get("cve_cvssv3_score"),
                    "vulab_severity": data.get("cve_cvssv3_severity"),
                    "asd_data": asd_data,
                }
            else:
                # 从cve analysis结果中获取数据失败，尝试从cve center数据源中获取
                raise Exception("no cve analysis result found in db")
        except Exception as err:
            self.logger.warning(f"没有找到cve analysis结果, error: {err}")
            # 如果cve analysis结果获取失败，则采用VULAB CVSS数据作为风险分析的基准
            for cvss in state["cvss_data"]:
                if cvss.cve_from == LABEL_VULAB:
                    return {
                        "cvss_vector": cvss.cvssv3_vector,
                        "vulab_score": cvss.cvssv3_score,
                        "vulab_severity": cvss.cvssv3_severity,
                        "asd_data": asd_data,
                    }

            # 没有VULAB cvss数据，选择查询到的redhat数据作为基准
            cvss_score_str = _calculate_cvss_score(state.get("statement_vector", ""))
            cvss_score_data = json.loads(cvss_score_str)
            vulab_score = cvss_score_data.get("base_score", 0.0)
            vulab_severity = cvss_score_data.get("base_severity", "Moderate")
            return {
                "cvss_vector": state.get("statement_vector", ""),
                "vulab_score": vulab_score,
                "vulab_severity": vulab_severity,
                "asd_data": asd_data,
                "error_message": "VULAB数据缺失",
            }

    def _generate_asd_data(self, state: CVSSVectorState):
        """生成ASD数据节点"""
        self.logger.info("开始生成ASD数据")
        try:
            cve_data = state["cve_data"]
            asd_data = self.asd_agent.run(cve_data)
            self.logger.info(f"ASD数据: {asd_data.scenario_description[:40]}...")
            return {
                "asd_data": asd_data.scenario_description,
            }
        except Exception as e:
            self.logger.error(f"生成ASD数据时发生错误: {e}")
            return {
                # CVSS分析中允许ASD数据为空，此时需要根据CVE的描述、CVSS向量，结合CVSS 3.1标准对当前的CVSS分析结果进行分析和理解，生成新的CVSS向量
                "asd_data": "",
                "error_message": f"生成ASD数据时发生错误: {str(e)}",
            }

    def _generate_cvss_vector(self, state: CVSSVectorState):
        """生成CVSS向量节点"""
        self.logger.info("开始完成CVE风险评估并生成CVSS向量")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS向量")
            asd_data = state.get("asd_data", "")
            prompt_template = self.prompt_manager.get_cvss_vector_assistant_prompt()
            self.llm.name = "CVSS Vector Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data={
                            "cve_id": cve_data.cve_id,
                            "cve_desc": cve_data.cve_desc,
                        },
                        cvss_vector=state.get("cvss_vector", ""),
                        statement_vector=state.get("statement_vector", ""),
                        statement=state.get("statement", ""),
                        asd_data=asd_data,
                    ),
                ]
            )
            self.logger.info("Generated CVSS Vector Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")

            # 解析响应
            # 自主风险评估结果格式为：
            # {
            #     "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            #     "cvss_severity": "Moderate",
            #     "score": 7.8,
            #     "statement": "CVE-2024-47685的风险差异化评估",
            #     "severity_statement": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            # }
            x_sec_cvss_vector = self._parse_json_response(response.content)
            cvss_score_str = _calculate_cvss_score(
                x_sec_cvss_vector.get("cvss_vector", "")
            )
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 0.0)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS向量时发生错误: {e}")
            cvss_vector = state.get("cvss_vector", "")
            statement = f"{state.get('messages', [])[0].content}的风险评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            return {
                "error_message": f"生成CVSS向量时发生错误: {str(e)}",
                "x_sec_cvss_vector": {
                    "cvss_vector": cvss_vector,
                    "score": 0.0,
                    "cvss_severity": "Moderate",
                    "statement": statement,
                },
            }

    def _generate_cvss_severity(self, state: CVSSVectorState):
        """生成CVSS严重性等级节点"""
        self.logger.info("开始生成CVSS严重性等级")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS严重性等级")
            filter_cve_data = {
                "cve_id": cve_data.cve_id,
                "cve_desc": cve_data.cve_desc,
            }
            x_sec_cvss_vector = state.get("x_sec_cvss_vector")
            if x_sec_cvss_vector is None:
                raise ValueError("CVSS向量数据为空，无法生成CVSS严重性等级")
            prompt_template = self.prompt_manager.get_cvss_severity_assistant_prompt()
            self.llm.name = "CVSS Severity Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data=filter_cve_data,
                        cvss_vector=state.get("x_sec_cvss_vector", {}).get(
                            "cvss_vector", ""
                        ),
                        original_cvss_vector=cve_data.cve_cvssv3_vector,
                        original_cvss_severity=cve_data.cve_cvssv3_severity,
                        asd_data=state.get("asd_data", ""),
                    ),
                ]
            )
            self.logger.info("Generated CVSS Severity Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")
            cvss_severity_data = self._parse_json_response(response.content)
            x_sec_cvss_vector_severity = cvss_severity_data.get(
                "cvss_severity", "Moderate"
            )
            x_sec_cvss_vector_severity_statement = cvss_severity_data.get(
                "severity_statement", ""
            )

            # 更新x_sec_cvss_vector中的严重性等级数据
            x_sec_cvss_vector["cvss_severity"] = x_sec_cvss_vector_severity
            x_sec_cvss_vector["severity_statement"] = (
                x_sec_cvss_vector_severity_statement
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS严重性等级时发生错误: {e}")
            x_sec_cvss_vector = state.get(
                "x_sec_cvss_vector",
                {  # 默认值，当LLM评估严重等级失败时使用
                    "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                    "score": 5.5,
                    "cvss_severity": "Moderate",
                    "severity_statement": "",
                },
            )
            cvss_score_str = _calculate_cvss_score(x_sec_cvss_vector["cvss_vector"])
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 5.5)
            x_sec_cvss_vector["severity_statement"] = (
                f"{x_sec_cvss_vector['cvss_vector']}的严重性等级评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "error_message": f"生成CVSS严重性等级时发生错误: {str(e)}",
            }

    def _parse_json_response(self, content: str):
        """解析JSON响应，支持多种格式"""
        try:
            # 清理响应内容
            content = content.strip()

            # 移除可能的markdown标记
            if content.startswith("```json"):
                content = content[7:]
            if content.endswith("```"):
                content = content[:-3]
            content = content.strip()

            # 尝试解析JSON
            return json.loads(content)

        except json.JSONDecodeError as e:
            self.logger.error(f"Failed to parse JSON response: {e}")
            return {}

    def run(self, message: str):
        """运行CVSS Vector Agent

        Args:
            message: 输入消息
        """
        thread_id = generate_thread_id("cvss-vector-agent")

        result = X_SEC_LLM.invoke(
            name="cvss-vector-agent",
            target=self.agent,
            tags=["cvss", "vector"],
            input=CVSSVectorState(messages=[HumanMessage(content=message)]),
            config={"configurable": {"thread_id": thread_id}},
        )
        # 返回结果格式为：
        # {
        #   "vulab": {
        #     "vulab_cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "vulab_score": 5.5,
        #     "vulab_severity": "Moderate",
        #   },
        #   "mimora": {
        #     "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "score": 5.5,
        #     "cvss_severity": "Moderate",
        #     "severity_statement": "",
        #   },
        #   "default": "vulab" or "mimora",
        # }

        # 制定default策略：
        # 1. 如果两个分数相等，则返回"mimora"
        # 2. 如果两个分数相差超过3分，则返回"vulab"
        # 3. 如果两个分数相差不超过3分，则返回"mimora"
        vulab_score = float(result.get("vulab_score", 0.0))
        mimora_score = float(result.get("x_sec_cvss_vector", {}).get("score", 0.0))
        if vulab_score == mimora_score:
            default = "mimora"
        elif abs(vulab_score - mimora_score) > 3:
            default = "vulab"
        else:
            default = "mimora"
        return {
            "vulab": {
                "cvss_vector": result.get("cvss_vector", ""),
                "score": result.get("vulab_score", 0.0),
                "severity": result.get("vulab_severity", "Moderate"),
            },
            "mimora": result.get("x_sec_cvss_vector", {}),
            "default": default,
        }


# 单例模式，添加全局变量，避免每次创建新的实例
_CVSS_VECTOR_AGENT = CVSSVectorAgent()


# 全局函数，用于CVE CVSS分析，返回JSON字符串
def cve_cvss_analysis(cve_id: str) -> str:
    """CVE CVSS分析"""
    try:
        result = _CVSS_VECTOR_AGENT.run(cve_id)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        _CVSS_VECTOR_AGENT.logger.error(f"CVE CVSS分析失败: {cve_id}, 错误: {str(e)}")
        return json.dumps({}, ensure_ascii=False)


if __name__ == "__main__":
    from internal.config_manager import init_config
    from internal.log import setup_logging

    init_config()
    setup_logging()

    cvss_vector_agent = CVSSVectorAgent()
    print(cvss_vector_agent.agent.get_graph().draw_ascii())
    print(cvss_vector_agent.agent.get_graph().draw_mermaid())
    # benchmark test
    cve_benchmark = [
        "CVE-2024-47685",
        "CVE-2025-40208",
        "CVE-2025-8194",
        "CVE-2025-59375",
        "CVE-2025-10725",
        "CVE-2025-40778",
        "CVE-2025-47277",
        "CVE-2025-5122",
    ]
    cve_benchmark = [
        "CVE-2025-39797",
        "CVE-2025-39833",
        "CVE-2025-21756",
        "CVE-2025-21934",
        "CVE-2025-38590",
        "CVE-2025-40269",
        "CVE-2025-14330",
        "CVE-2025-14321",
    ]
    cve_benchmark = ["cve-2024-47685"]
    result_list = []
    for cve_id in cve_benchmark:
        result = cvss_vector_agent.run(cve_id)
        result_list.append({"cve_id": cve_id, "result": result})

    print("==" * 50)
    print(json.dumps(result_list, indent=2, ensure_ascii=False))
    # mermaid string for agent graph
    graph_mermaid_string = """
    ---
    config:
      flowchart:
        curve: linear
    ---
    graph TD;
        __start__([<p>__start__</p>]):::first
        get_cve_data@{ shape: tag-doc, label: "cve data sub-agent " }
        get_cvss_data@{ shape: tag-doc, label: "cvss data sub-agent " }
        get_cvss_statement_data@{ shape: lin-rect, label: "cvss extra datasub-agent" }
        normalize_cvss_data(normalize_cvss_data)
        generate_asd_data@{ shape: tag-rect, label: "MITRE TTP threat modelsub-agent" }
        generate_cvss_vector(generate_cvss_vector)
        __end__([<p>__end__</p>]):::last
        __start__ --> get_cve_data;
        __start__ --> get_cvss_data;
        generate_asd_data --> normalize_cvss_data;
        get_cve_data -.-> generate_asd_data;
        get_cve_data -.-> normalize_cvss_data;
        get_cvss_data -.-> get_cvss_statement_data;
        get_cvss_data -.-> normalize_cvss_data;
        get_cvss_statement_data --> normalize_cvss_data;
        normalize_cvss_data --> generate_cvss_vector;
        generate_cvss_vector --> __end__;
        classDef default fill:#f2f0ff,line-height:1.2
        classDef first fill-opacity:0
        classDef last fill:#bfb6fc
        style get_cve_data fill:#939,color:#fff
        style get_cvss_data fill:#939,color:#fff
        style generate_asd_data fill:#696,stroke:#333,color:#fff
    """

```

现在让我检查并更新文件底部的 mermaid 图注释：

```python
"""
CVSS Vector Agent

CVSS Vector Agent负责根据CVE数据以及受影响产品组件的组件画像信息以及根据TTP模型建立的CVE ASD数据，差异化分析产品的CVE风险并依据CVSS 3.1标准重新生成CVSS向量和打分。
"""

import json
import re
from typing import Optional, List, Annotated

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_community.chat_models import ChatTongyi
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from pydantic import Field, field_validator


def _keep_non_empty_str_reducer(current: str, new: str) -> str:
    """字符串类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字段时，由于并发执行时reducer的调用顺序不确定，
    该函数采用"保留有内容的值"策略：
    - 如果current有内容，保留current
    - 如果current为空但new有内容，使用new
    - 如果都为空，返回空字符串

    这样确保有实际数据的节点产生的值不会被其他节点的空值覆盖。
    """
    # 优先保留有内容的数据
    if current and len(current.strip()) > 0:
        return current
    if new and len(new.strip()) > 0:
        return new
    return ""


def _keep_non_empty_dict_reducer(current: dict, new: dict) -> dict:
    """字典类型字段的reducer函数，用于处理并发更新

    当多个节点并发执行并同时更新同一字典字段时（如 x_sec_cvss_vector），
    该函数采用"合并更新"策略：
    - 以当前值为基础
    - 用新值覆盖/更新已有字段
    - 保留新值中新增的字段

    这样确保并发节点产生的数据可以正确合并，后执行的节点可以更新前面节点设置的值。
    """
    if current is None:
        current = {}
    if new is None:
        new = {}
    # 创建副本避免修改原对象
    result = dict(current)
    # 合并新值，新值覆盖旧值
    result.update(new)
    return result


from core.agents.mimora.tools.cvss_tool import _calculate_cvss_score
from core.agents.mimora.tools.cvss_data_tool import get_redhat_cve_details
from core.agents.mimora.data_type import LABEL_REDHAT, LABEL_VULAB, CVEData, CVSSData
from core.agents.mimora.cve_data_agent import CVEDataAgent, CVEDataState
from core.agents.mimora.mitre_asd_agent import MitreASDAgent
from core.models.llm_manager import X_SEC_LLM
from core.storage.db import CVECleanDBBase
from internal.log import get_logger
from internal.prompt_manager.prompts import CVSSVectorPrompts
from internal.thread_manager import generate_thread_id
from internal.config_manager import get_settings
from server.auth.api_key import get_db


class CVSSVectorState(MessagesState):
    cve_data: CVEData = Field(..., description="cve data")
    cvss_data: List[CVSSData] = Field(
        ..., description="cvss data list from cve source data"
    )
    cvss_vector: str = Field(..., description="cvss vector string")
    vulab_score: float = Field(..., description="vulab cvss score")
    vulab_severity: str = Field(..., description="vulab cvss severity")
    # 使用Annotated配合reducer处理并发更新，避免get_cvss_statement_data和normalize_cvss_data同时写入时的冲突
    statement_vector: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement vector string"
    )
    statement: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="cvss statement string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_asd_data和normalize_cvss_data同时写入时的冲突
    asd_data: Annotated[str, _keep_non_empty_str_reducer] = Field(
        default="", description="asd data string"
    )
    # 使用Annotated配合reducer处理并发更新，避免generate_cvss_vector和generate_cvss_severity同时写入时的冲突
    x_sec_cvss_vector: Annotated[dict, _keep_non_empty_dict_reducer] = Field(
        default_factory=dict, description="x-sec cvss vector object"
    )


class CVSSVectorAgent:
    """CVSS Vector Agent"""

    def __init__(self):
        self.data_agent = CVEDataAgent()
        self.asd_agent = MitreASDAgent()
        self.llm = ChatTongyi(name="cvss-vector-agent-llm", model="qwen3-max")
        self.prompt_manager = CVSSVectorPrompts()
        self.memory = MemorySaver()
        self.agent = self._build_graph()
        self.logger = get_logger()

    def _build_graph(self):
        """构建CVSS Vector Agent的图结构

        工作流程：
        1. 并行获取需要的数据包括：cve data、cvss data、cve asd data
        2. 根据cvss data查看是否需要获取额外CVSS Statement数据
        3. 将上面获取的数据进行归一化
        4. 将归一化后的数据进行CVE风险评估与CVSS向量差异化评估

        使用 barrier 节点同步所有路径，确保 normalize_cvss_data 只执行一次。
        新增两个 barrier 节点：
        - cve_path_barrier: 同步 CVE 数据获取路径（无论是否生成 ASD 数据）
        - cvss_path_barrier: 同步 CVSS 数据获取路径（无论是否获取 Statement 数据）

        图结构如下：
                                            +-----------+
                                          **| __start__ |**
                                     *****  +-----------+  ******
                              *******                            *****
                         *****                                        ******
                     ****                                                   ***
       +--------------+                                                    +---------------+
       | get_cve_data |                                                    | get_cvss_data |
       +--------------+                                                    +---------------+
            |                                                                      |
       [条件边]                                                               [条件边]
         /    \\                                                                /    \\
        ↓      ↓                                                              ↓      ↓
  +-----------+  +------------------+                    +------------------+  +------------+
  | asd_data  |  | cve_path_barrier |                    | cvss_path_barrier|  | statement  |
  +-----------+  +------------------+                    +------------------+  +------------+
        \\            /                                            \\            /
         \\          /                                              \\          /
          ↓        ↓                                                ↓        ↓
       +------------------+                                    +-------------------+
       | cve_path_barrier |                                    | cvss_path_barrier |
       +------------------+                                    +-------------------+
                   \\                                                    /
                    \\                                                  /
                     ↓                                                ↓
                      +---------------------------------------------+
                      |            normalize_cvss_data              |
                      +---------------------------------------------+
                                           |
                                           ↓
                      +---------------------------------------------+
                      |            generate_cvss_vector             |
                      +---------------------------------------------+
                                           |
                                           ↓
                      +---------------------------------------------+
                      |            generate_cvss_severity           |
                      +---------------------------------------------+
                                           |
                                           ↓
                                      +---------+
                                      | __end__ |
                                      +---------+
        """
        builder = StateGraph(CVSSVectorState)

        # 添加原有节点
        builder.add_node("get_cve_data", self._get_cve_data)
        builder.add_node("get_cvss_data", self._get_cvss_data)
        builder.add_node("get_cvss_statement_data", self._get_cvss_statement_data)
        builder.add_node("normalize_cvss_data", self._normalize_cvss_data)
        builder.add_node("generate_asd_data", self._generate_asd_data)
        builder.add_node("generate_cvss_vector", self._generate_cvss_vector)
        builder.add_node("generate_cvss_severity", self._generate_cvss_severity)

        # 添加 barrier 节点 - 用于同步路径，确保下游节点只执行一次
        builder.add_node("cve_path_barrier", self._cve_path_barrier)
        builder.add_node("cvss_path_barrier", self._cvss_path_barrier)

        # **TODO**: 后面添加组件画像节点用于增强针对产品差异化的cvss风险评估分析
        # builder.add_node("generate_component_profile")

        # 添加边
        # get_cve_data, get_cvss_data 是并行节点用于加速agent执行
        builder.add_edge(START, "get_cve_data")
        builder.add_edge(START, "get_cvss_data")

        # CVE 数据路径：条件边选择走 generate_asd_data 或直接走 cve_path_barrier
        # 无论哪条路径，最终都会到达 cve_path_barrier，确保路径长度一致
        builder.add_conditional_edges(
            "get_cve_data",
            self._is_cve_data_empty,
            ["generate_asd_data", "cve_path_barrier"],
        )
        # generate_asd_data 完成后也到 cve_path_barrier
        builder.add_edge("generate_asd_data", "cve_path_barrier")

        # CVSS 数据路径：条件边选择走 get_cvss_statement_data 或直接走 cvss_path_barrier
        # 无论哪条路径，最终都会到达 cvss_path_barrier，确保路径长度一致
        builder.add_conditional_edges(
            "get_cvss_data",
            self._cvss_data_check,
            ["get_cvss_statement_data", "cvss_path_barrier"],
        )
        # get_cvss_statement_data 完成后也到 cvss_path_barrier
        builder.add_edge("get_cvss_statement_data", "cvss_path_barrier")

        # 两个 barrier 节点都汇合到 normalize_cvss_data
        # LangGraph 会等待两个 barrier 都完成后才执行 normalize_cvss_data（只执行一次）
        builder.add_edge("cve_path_barrier", "normalize_cvss_data")
        builder.add_edge("cvss_path_barrier", "normalize_cvss_data")

        # 后续节点保持线性执行
        builder.add_edge("normalize_cvss_data", "generate_cvss_vector")
        builder.add_edge("generate_cvss_vector", "generate_cvss_severity")
        builder.add_edge("generate_cvss_severity", END)

        return builder.compile(
            checkpointer=self.memory,
        )

    def _cve_path_barrier(self, state: CVSSVectorState):
        """CVE 数据路径的 barrier 节点

        这是一个同步屏障节点，用于确保 CVE 数据获取路径（无论是否生成 ASD 数据）
        在继续执行前完成同步。不执行任何实际操作，只是作为路径汇合点。
        """
        self.logger.debug("CVE 路径 barrier 节点执行完成")
        return {}

    def _cvss_path_barrier(self, state: CVSSVectorState):
        """CVSS 数据路径的 barrier 节点

        这是一个同步屏障节点，用于确保 CVSS 数据获取路径（无论是否获取 Statement 数据）
        在继续执行前完成同步。不执行任何实际操作，只是作为路径汇合点。
        """
        self.logger.debug("CVSS 路径 barrier 节点执行完成")
        return {}

    def _is_cve_data_empty(self, state: CVSSVectorState):
        """检查CVE数据是否为空

        返回值：
        - "generate_asd_data": 如果有 CVE 数据，走 ASD 生成路径
        - "cve_path_barrier": 如果没有 CVE 数据，直接走 barrier 节点
        """
        if state.get("cve_data") is None:
            return "cve_path_barrier"
        return "generate_asd_data"

    def _cvss_data_check(self, state: CVSSVectorState):
        """检查CVSS数据是否为空

        返回值：
        - "get_cvss_statement_data": 如果需要获取额外的 CVSS Statement 数据
        - "cvss_path_barrier": 如果不需要或数据为空，直接走 barrier 节点
        """
        if state.get("cvss_data") is None:
            return "cvss_path_barrier"
        return self._cvss_vector_check(state)

    def _get_cve_data(self, state: CVSSVectorState):
        """CVE数据Agent节点"""
        self.logger.info("开始获取CVE数据")

        try:
            # 从消息中提取CVE查询内容
            if state["messages"] is None:
                return {
                    "cve_data": None,
                    "error_message": "没有提供CVE查询消息",
                }

            # 获取最后一条用户消息
            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cve_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cve_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"请查询CVE数据: {last_message.content}"])
            )

            # 检查是否有cve_data字段（成功情况）
            if "cve_data" in cve_result and cve_result["cve_data"]:
                cve_data = cve_result["cve_data"]
                self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                return {
                    "cve_data": cve_data,
                    "messages": state["messages"],
                }

            # 如果没有cve_data，尝试从最后一条消息解析
            last_message = cve_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    import json

                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cve_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVE数据: {data.get('error_message')}",
                        }
                    cve_data = CVEData(**data)
                    self.logger.info(f"成功获取CVE数据: {cve_data.cve_id}")

                    return {
                        "cve_data": cve_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cve_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cve_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }

        except Exception as e:
            self.logger.error(f"获取CVE数据时发生错误: {e}")
            return {
                "cve_data": None,
                "error_message": f"获取CVE数据时发生错误: {str(e)}",
            }

    def _get_cvss_data(self, state: CVSSVectorState):
        """CVSS数据Agent节点"""
        self.logger.info("开始获取CVSS数据")
        try:
            if state["messages"] is None:
                return {
                    "cvss_data": None,
                    "error_message": "没有提供CVSS查询消息",
                }

            last_message = state["messages"][-1]
            if last_message.type != "human":
                return {
                    "cvss_data": None,
                    "error_message": "最后一条消息不是用户消息",
                }

            cvss_result = self.data_agent.agent.invoke(
                CVEDataState(messages=[f"查询CVE CVSS数据: {last_message.content}"])
            )

            # 检查是否有cvss_data字段（成功情况）
            if "cvss_data" in cvss_result and cvss_result["cvss_data"]:
                cvss_data = cvss_result["cvss_data"]
                self.logger.info(f"成功获取CVSS数据: {cvss_data}")
                return {
                    "cvss_data": cvss_data,
                    "messages": state["messages"],
                }
            # 如果没有cvss_data，尝试从最后一条消息解析
            last_message = cvss_result["messages"][-1]
            if last_message.type == "tool":
                try:
                    data = json.loads(last_message.content)
                    # 检查是否是错误结果
                    if isinstance(data, dict) and data.get("error"):
                        self.logger.error(f"工具执行失败: {data.get('error_message')}")
                        return {
                            "cvss_data": None,
                            "error_message": f"CVE Agent未能获取到有效的CVSS数据: {data.get('error_message')}",
                        }
                    cvss_data = [CVSSData(**item) for item in data]
                    self.logger.info(f"成功获取CVSS数据: {cvss_data}")

                    return {
                        "cvss_data": cvss_data,
                        "messages": state["messages"],
                    }
                except (json.JSONDecodeError, ValueError) as e:
                    self.logger.error(f"解析结果失败: {e}")
                    return {
                        "cvss_data": None,
                        "error_message": f"CVE Agent解析结果失败: {str(e)}",
                    }

            return {
                "cvss_data": None,
                "error_message": "CVE Agent未能获取到有效的CVE数据",
            }
        except Exception as e:
            self.logger.error(f"获取CVSS数据时发生错误: {e}")
            return {
                "cvss_data": None,
                "error_message": f"获取CVSS数据时发生错误: {str(e)}",
            }

    def _cvss_vector_check(self, state: CVSSVectorState):
        """CVSS向量检查节点，检查原始数据中CVSS是否一致

        返回值：
        - "get_cvss_statement_data": 如果需要获取额外的 CVSS Statement 数据（有 RedHat 数据源）
        - "cvss_path_barrier": 其他情况直接走 barrier 节点
        """
        self.logger.info("开始检查CVSS向量")
        try:
            if state.get("cvss_data") is None:
                state["error_message"] = "没有提供CVSS数据"
                return "cvss_path_barrier"

            cvss_data_list = state["cvss_data"]
            cvss_data_len = len(cvss_data_list)
            match cvss_data_len:
                case 0:
                    state["error_message"] = "没有提供CVSS数据"
                    return "cvss_path_barrier"
                case 1:
                    cvss_data = cvss_data_list[0]
                    state["cvss_vector"] = cvss_data.cvssv3_vector
                    return "cvss_path_barrier"
                case _:
                    for cvss in cvss_data_list:
                        if cvss.cve_from == LABEL_REDHAT:
                            return "get_cvss_statement_data"

                    # 如果没有redhat数据，则返回cvss_path_barrier
                    return "cvss_path_barrier"

        except Exception as e:
            self.logger.error(f"检查CVSS向量时发生错误: {e}")
            return "cvss_path_barrier"

    def _get_cvss_statement_data(self, state: CVSSVectorState):
        """获取额外CVSS Statement数据节点"""
        self.logger.info("开始获取额外CVSS Statement数据")
        try:
            if state.get("cve_data") is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "没有提供CVE数据",
                }

            cve_data = state["cve_data"]
            cve_id = cve_data.cve_id
            data = get_redhat_cve_details(cve_id)
            if data is None:
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "无法获取Redhat CVE详情数据",
                }

            # 提取data字段
            redhat_data = data.get("data", {})
            if not isinstance(redhat_data, dict):
                return {
                    "statement": "",
                    "statement_vector": "",
                    "error_message": "Redhat CVE详情数据格式错误",
                }
            redhat_statement = redhat_data.get("field_cve_statement_text", {}).get(
                "safe_value", None
            )
            redhat_statement_vector = redhat_data.get(
                "field_cve_cvss3_base_metrics", {}
            ).get("safe_value", None)
            return {
                "statement": redhat_statement,
                "statement_vector": redhat_statement_vector,
            }

        except Exception as e:
            self.logger.error(f"获取额外CVSS Statement数据时发生错误: {e}")
            return {
                # 当无法正确获取statement数据的时候，CVSS Agent分析中允许Statement数据为空
                "statement": "",
                "statement_vector": "",
                "error_message": f"获取额外CVSS Statement数据时发生错误: {str(e)}",
            }

    def _normalize_cvss_data(self, state: CVSSVectorState):
        """归一化CVSS向量数据节点"""
        self.logger.info("开始归一化CVSS向量数据")
        # 确保 asd_data 字段存在，如果 generate_asd_data 节点未执行，则初始化为空字符串
        asd_data = state.get("asd_data", "")
        # 处理数据生成异常的情况
        if state.get("cve_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVE数据",
            }
        if state.get("cvss_data") is None:
            return {
                "cve_data": None,
                "cvss_data": [],
                "vulab_score": 0.0,
                "vulab_severity": "Moderate",
                "statement": "",
                "statement_vector": "",
                "asd_data": asd_data,
                "error_message": "没有提供CVSS数据",
            }

        try:
            # 尝试从cve analysis分析结果中获取cve风险评估数据
            db: CVECleanDBBase = get_db(settings=get_settings())
            cve_id = state["cve_data"].cve_id
            cvs_analysis_results = sorted(
                db.get_cve_clean_result_by_cve_id(cve_id),
                key=lambda x: x.created_time,
                reverse=True,
            )
            if len(cvs_analysis_results) > 0:
                cvs_analysis_result = cvs_analysis_results[0]
                data = json.loads(cvs_analysis_result.result_data)
                # 从结果中获取cvss数据
                # result_data的格式是：
                # {
                #   "cve_id": "CVE-2025-13601",
                #   "cve_cvssv3_score": "7.7",
                #   "cve_cvssv3_severity": "Moderate",
                #   "cve_cvssv3_vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H",
                #   "cve_cvssv3_from": "redhat",
                #   ...
                # }
                return {
                    "cvss_vector": data.get("cve_cvssv3_vector"),
                    "vulab_score": data.get("cve_cvssv3_score"),
                    "vulab_severity": data.get("cve_cvssv3_severity"),
                    "asd_data": asd_data,
                }
            else:
                # 从cve analysis结果中获取数据失败，尝试从cve center数据源中获取
                raise Exception("no cve analysis result found in db")
        except Exception as err:
            self.logger.warning(f"没有找到cve analysis结果, error: {err}")
            # 如果cve analysis结果获取失败，则采用VULAB CVSS数据作为风险分析的基准
            for cvss in state["cvss_data"]:
                if cvss.cve_from == LABEL_VULAB:
                    return {
                        "cvss_vector": cvss.cvssv3_vector,
                        "vulab_score": cvss.cvssv3_score,
                        "vulab_severity": cvss.cvssv3_severity,
                        "asd_data": asd_data,
                    }

            # 没有VULAB cvss数据，选择查询到的redhat数据作为基准
            cvss_score_str = _calculate_cvss_score(state.get("statement_vector", ""))
            cvss_score_data = json.loads(cvss_score_str)
            vulab_score = cvss_score_data.get("base_score", 0.0)
            vulab_severity = cvss_score_data.get("base_severity", "Moderate")
            return {
                "cvss_vector": state.get("statement_vector", ""),
                "vulab_score": vulab_score,
                "vulab_severity": vulab_severity,
                "asd_data": asd_data,
                "error_message": "VULAB数据缺失",
            }

    def _generate_asd_data(self, state: CVSSVectorState):
        """生成ASD数据节点"""
        self.logger.info("开始生成ASD数据")
        try:
            cve_data = state["cve_data"]
            asd_data = self.asd_agent.run(cve_data)
            self.logger.info(f"ASD数据: {asd_data.scenario_description[:40]}...")
            return {
                "asd_data": asd_data.scenario_description,
            }
        except Exception as e:
            self.logger.error(f"生成ASD数据时发生错误: {e}")
            return {
                # CVSS分析中允许ASD数据为空，此时需要根据CVE的描述、CVSS向量，结合CVSS 3.1标准对当前的CVSS分析结果进行分析和理解，生成新的CVSS向量
                "asd_data": "",
                "error_message": f"生成ASD数据时发生错误: {str(e)}",
            }

    def _generate_cvss_vector(self, state: CVSSVectorState):
        """生成CVSS向量节点"""
        self.logger.info("开始完成CVE风险评估并生成CVSS向量")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS向量")
            asd_data = state.get("asd_data", "")
            prompt_template = self.prompt_manager.get_cvss_vector_assistant_prompt()
            self.llm.name = "CVSS Vector Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data={
                            "cve_id": cve_data.cve_id,
                            "cve_desc": cve_data.cve_desc,
                        },
                        cvss_vector=state.get("cvss_vector", ""),
                        statement_vector=state.get("statement_vector", ""),
                        statement=state.get("statement", ""),
                        asd_data=asd_data,
                    ),
                ]
            )
            self.logger.info("Generated CVSS Vector Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")

            # 解析响应
            # 自主风险评估结果格式为：
            # {
            #     "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            #     "cvss_severity": "Moderate",
            #     "score": 7.8,
            #     "statement": "CVE-2024-47685的风险差异化评估",
            #     "severity_statement": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
            # }
            x_sec_cvss_vector = self._parse_json_response(response.content)
            cvss_score_str = _calculate_cvss_score(
                x_sec_cvss_vector.get("cvss_vector", "")
            )
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 0.0)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS向量时发生错误: {e}")
            cvss_vector = state.get("cvss_vector", "")
            statement = f"{state.get('messages', [])[0].content}的风险评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            return {
                "error_message": f"生成CVSS向量时发生错误: {str(e)}",
                "x_sec_cvss_vector": {
                    "cvss_vector": cvss_vector,
                    "score": 0.0,
                    "cvss_severity": "Moderate",
                    "statement": statement,
                },
            }

    def _generate_cvss_severity(self, state: CVSSVectorState):
        """生成CVSS严重性等级节点"""
        self.logger.info("开始生成CVSS严重性等级")
        try:
            cve_data = state.get("cve_data")
            if cve_data is None:
                raise ValueError("CVE数据为空，无法生成CVSS严重性等级")
            filter_cve_data = {
                "cve_id": cve_data.cve_id,
                "cve_desc": cve_data.cve_desc,
            }
            x_sec_cvss_vector = state.get("x_sec_cvss_vector")
            if x_sec_cvss_vector is None:
                raise ValueError("CVSS向量数据为空，无法生成CVSS严重性等级")
            prompt_template = self.prompt_manager.get_cvss_severity_assistant_prompt()
            self.llm.name = "CVSS Severity Assistant"
            response = self.llm.invoke(
                [
                    prompt_template.system,
                    prompt_template.human.format(
                        cve_data=filter_cve_data,
                        cvss_vector=state.get("x_sec_cvss_vector", {}).get(
                            "cvss_vector", ""
                        ),
                        original_cvss_vector=cve_data.cve_cvssv3_vector,
                        original_cvss_severity=cve_data.cve_cvssv3_severity,
                        asd_data=state.get("asd_data", ""),
                    ),
                ]
            )
            self.logger.info("Generated CVSS Severity Response")
            self.logger.debug(f"Response content: {response.content[:40]}...")
            cvss_severity_data = self._parse_json_response(response.content)
            x_sec_cvss_vector_severity = cvss_severity_data.get(
                "cvss_severity", "Moderate"
            )
            x_sec_cvss_vector_severity_statement = cvss_severity_data.get(
                "severity_statement", ""
            )

            # 更新x_sec_cvss_vector中的严重性等级数据
            x_sec_cvss_vector["cvss_severity"] = x_sec_cvss_vector_severity
            x_sec_cvss_vector["severity_statement"] = (
                x_sec_cvss_vector_severity_statement
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "messages": [response],
            }
        except Exception as e:
            self.logger.error(f"生成CVSS严重性等级时发生错误: {e}")
            x_sec_cvss_vector = state.get(
                "x_sec_cvss_vector",
                {  # 默认值，当LLM评估严重等级失败时使用
                    "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                    "score": 5.5,
                    "cvss_severity": "Moderate",
                    "severity_statement": "",
                },
            )
            cvss_score_str = _calculate_cvss_score(x_sec_cvss_vector["cvss_vector"])
            cvss_score_data = json.loads(cvss_score_str)
            x_sec_cvss_vector["cvss_severity"] = cvss_score_data.get(
                "base_severity", "Moderate"
            )
            x_sec_cvss_vector["score"] = cvss_score_data.get("base_score", 5.5)
            x_sec_cvss_vector["severity_statement"] = (
                f"{x_sec_cvss_vector['cvss_vector']}的严重性等级评估发生错误(错误信息: {str(e)})，请转人工重新评估"
            )
            return {
                "x_sec_cvss_vector": x_sec_cvss_vector,
                "error_message": f"生成CVSS严重性等级时发生错误: {str(e)}",
            }

    def _parse_json_response(self, content: str):
        """解析JSON响应，支持多种格式"""
        try:
            # 清理响应内容
            content = content.strip()

            # 移除可能的markdown标记
            if content.startswith("```json"):
                content = content[7:]
            if content.endswith("```"):
                content = content[:-3]
            content = content.strip()

            # 尝试解析JSON
            return json.loads(content)

        except json.JSONDecodeError as e:
            self.logger.error(f"Failed to parse JSON response: {e}")
            return {}

    def run(self, message: str):
        """运行CVSS Vector Agent

        Args:
            message: 输入消息
        """
        thread_id = generate_thread_id("cvss-vector-agent")

        result = X_SEC_LLM.invoke(
            name="cvss-vector-agent",
            target=self.agent,
            tags=["cvss", "vector"],
            input=CVSSVectorState(messages=[HumanMessage(content=message)]),
            config={"configurable": {"thread_id": thread_id}},
        )
        # 返回结果格式为：
        # {
        #   "vulab": {
        #     "vulab_cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "vulab_score": 5.5,
        #     "vulab_severity": "Moderate",
        #   },
        #   "mimora": {
        #     "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
        #     "score": 5.5,
        #     "cvss_severity": "Moderate",
        #     "severity_statement": "",
        #   },
        #   "default": "vulab" or "mimora",
        # }

        # 制定default策略：
        # 1. 如果两个分数相等，则返回"mimora"
        # 2. 如果两个分数相差超过3分，则返回"vulab"
        # 3. 如果两个分数相差不超过3分，则返回"mimora"
        vulab_score = float(result.get("vulab_score", 0.0))
        mimora_score = float(result.get("x_sec_cvss_vector", {}).get("score", 0.0))
        if vulab_score == mimora_score:
            default = "mimora"
        elif abs(vulab_score - mimora_score) > 3:
            default = "vulab"
        else:
            default = "mimora"
        return {
            "vulab": {
                "cvss_vector": result.get("cvss_vector", ""),
                "score": result.get("vulab_score", 0.0),
                "severity": result.get("vulab_severity", "Moderate"),
            },
            "mimora": result.get("x_sec_cvss_vector", {}),
            "default": default,
        }


# 单例模式，添加全局变量，避免每次创建新的实例
_CVSS_VECTOR_AGENT = CVSSVectorAgent()


# 全局函数，用于CVE CVSS分析，返回JSON字符串
def cve_cvss_analysis(cve_id: str) -> str:
    """CVE CVSS分析"""
    try:
        result = _CVSS_VECTOR_AGENT.run(cve_id)
        return json.dumps(result, ensure_ascii=False)
    except Exception as e:
        _CVSS_VECTOR_AGENT.logger.error(f"CVE CVSS分析失败: {cve_id}, 错误: {str(e)}")
        return json.dumps({}, ensure_ascii=False)


if __name__ == "__main__":
    from internal.config_manager import init_config
    from internal.log import setup_logging

    init_config()
    setup_logging()

    cvss_vector_agent = CVSSVectorAgent()
    print(cvss_vector_agent.agent.get_graph().draw_ascii())
    print(cvss_vector_agent.agent.get_graph().draw_mermaid())
    # benchmark test
    cve_benchmark = [
        "CVE-2024-47685",
        "CVE-2025-40208",
        "CVE-2025-8194",
        "CVE-2025-59375",
        "CVE-2025-10725",
        "CVE-2025-40778",
        "CVE-2025-47277",
        "CVE-2025-5122",
    ]
    cve_benchmark = [
        "CVE-2025-39797",
        "CVE-2025-39833",
        "CVE-2025-21756",
        "CVE-2025-21934",
        "CVE-2025-38590",
        "CVE-2025-40269",
        "CVE-2025-14330",
        "CVE-2025-14321",
    ]
    cve_benchmark = ["cve-2024-47685"]
    result_list = []
    for cve_id in cve_benchmark:
        result = cvss_vector_agent.run(cve_id)
        result_list.append({"cve_id": cve_id, "result": result})

    print("==" * 50)
    print(json.dumps(result_list, indent=2, ensure_ascii=False))
    # mermaid string for agent graph (使用 barrier 节点同步路径)
    graph_mermaid_string = """
    ---
    config:
      flowchart:
        curve: linear
    ---
    graph TD;
        __start__([<p>__start__</p>]):::first
        get_cve_data@{ shape: tag-doc, label: "cve data sub-agent" }
        get_cvss_data@{ shape: tag-doc, label: "cvss data sub-agent" }
        generate_asd_data@{ shape: tag-rect, label: "MITRE TTP threat model sub-agent" }
        get_cvss_statement_data@{ shape: lin-rect, label: "cvss extra data sub-agent" }
        cve_path_barrier([cve_path_barrier]):::barrier
        cvss_path_barrier([cvss_path_barrier]):::barrier
        normalize_cvss_data(normalize_cvss_data)
        generate_cvss_vector(generate_cvss_vector)
        generate_cvss_severity(generate_cvss_severity)
        __end__([<p>__end__</p>]):::last

        __start__ --> get_cve_data;
        __start__ --> get_cvss_data;

        get_cve_data -.-> generate_asd_data;
        get_cve_data -.-> cve_path_barrier;
        generate_asd_data --> cve_path_barrier;

        get_cvss_data -.-> get_cvss_statement_data;
        get_cvss_data -.-> cvss_path_barrier;
        get_cvss_statement_data --> cvss_path_barrier;

        cve_path_barrier --> normalize_cvss_data;
        cvss_path_barrier --> normalize_cvss_data;

        normalize_cvss_data --> generate_cvss_vector;
        generate_cvss_vector --> generate_cvss_severity;
        generate_cvss_severity --> __end__;

        classDef default fill:#f2f0ff,line-height:1.2
        classDef first fill-opacity:0
        classDef last fill:#bfb6fc
        classDef barrier fill:#f90,stroke:#333,color:#fff
        style get_cve_data fill:#939,color:#fff
        style get_cvss_data fill:#939,color:#fff
        style generate_asd_data fill:#696,stroke:#333,color:#fff
    """

```

让我验证修改后的图结构代码：

## 修改完成

已成功使用显式 barrier 节点重构 CVSS Vector Agent 的图结构。以下是修改摘要：

### 新增的 Barrier 节点

```python
def _cve_path_barrier(self, state: CVSSVectorState):
    """CVE 数据路径的 barrier 节点 - 同步屏障"""
    self.logger.debug("CVE 路径 barrier 节点执行完成")
    return {}

def _cvss_path_barrier(self, state: CVSSVectorState):
    """CVSS 数据路径的 barrier 节点 - 同步屏障"""
    self.logger.debug("CVSS 路径 barrier 节点执行完成")
    return {}
```

### 新的图结构

```
                    START
                   /      \
                  ↓        ↓
         get_cve_data    get_cvss_data
              |               |
         [条件边]         [条件边]
           /    \           /    \
          ↓      ↓         ↓      ↓
    asd_data    ↓    statement    ↓
         |      |         |       |
         ↓      ↓         ↓       ↓
       cve_path_barrier   cvss_path_barrier
                \            /
                 \          /
                  ↓        ↓
            normalize_cvss_data  ← 只执行一次！
                    ↓
            generate_cvss_vector
                    ↓
            generate_cvss_severity
                    ↓
                   END
```

### 关键改动

| 组件 | 之前 | 现在 |
|------|------|------|
| 条件边目标 | 直接跳到 `normalize_cvss_data` | 跳到 `cve_path_barrier` 或 `cvss_path_barrier` |
| 路径长度 | 不一致（1~2 跳） | 统一（都是 2 跳到 barrier） |
| `normalize_cvss_data` 入边 | 来自 4 个不同节点 | 只来自 2 个 barrier 节点 |
| 执行次数 | 可能多次 | 保证只执行一次 |

### 执行流程示意

```
时间 →
─────────────────────────────────────────────────────────────────

超步1: get_cve_data (并行) get_cvss_data
               ↓                 ↓
超步2: generate_asd_data     cvss_path_barrier (等待)
               ↓                 
超步3: cve_path_barrier      cvss_path_barrier ← 两个 barrier 都完成
               ↓                 ↓
超步4:         normalize_cvss_data ← 只执行一次！
                       ↓
超步5:         generate_cvss_vector
                       ↓
超步6:         generate_cvss_severity
                       ↓
               END
```

现在 `normalize_cvss_data` 只会在两个 barrier 节点都完成后执行一次，彻底解决了节点重复执行的问题。同时你之前添加的 reducer 函数仍然保留作为额外的安全保障。

```python
def _cve_path_barrier(self, state: CVSSVectorState):
    """CVE 数据路径的 barrier 节点 - 同步屏障"""
    self.logger.debug("CVE 路径 barrier 节点执行完成")
    return {}

def _cvss_path_barrier(self, state: CVSSVectorState):
    """CVSS 数据路径的 barrier 节点 - 同步屏障"""
    self.logger.debug("CVSS 路径 barrier 节点执行完成")
    return {}
```

```plaintext
                    START
                   /      \
                  ↓        ↓
         get_cve_data    get_cvss_data
              |               |
         [条件边]         [条件边]
           /    \           /    \
          ↓      ↓         ↓      ↓
    asd_data    ↓    statement    ↓
         |      |         |       |
         ↓      ↓         ↓       ↓
       cve_path_barrier   cvss_path_barrier
                \            /
                 \          /
                  ↓        ↓
            normalize_cvss_data  ← 只执行一次！
                    ↓
            generate_cvss_vector
                    ↓
            generate_cvss_severity
                    ↓
                   END
```

```plaintext
时间 →
─────────────────────────────────────────────────────────────────

超步1: get_cve_data (并行) get_cvss_data
               ↓                 ↓
超步2: generate_asd_data     cvss_path_barrier (等待)
               ↓                 
超步3: cve_path_barrier      cvss_path_barrier ← 两个 barrier 都完成
               ↓                 ↓
超步4:         normalize_cvss_data ← 只执行一次！
                       ↓
超步5:         generate_cvss_vector
                       ↓
超步6:         generate_cvss_severity
                       ↓
               END
```

---

**User**

@zsh (1598-1619) 单元测试运行失败，分析一下原因并修复

---

**Cursor**


测试失败是因为条件边返回值已改为 barrier 节点，而测试仍期望旧值。查看测试文件并修复：



更新这三个测试用例，将期望值改为新的 barrier 节点名称：


```python
"""
CVSS Vector Agent 单元测试
专注于测试CVSSVectorAgent的各个节点函数、状态管理、数据处理等
"""

import json
import os
import sys
from unittest.mock import MagicMock, patch, PropertyMock

import pytest
from pydantic import ValidationError

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))

from core.agents.mimora.data_type import (
    LABEL_REDHAT,
    LABEL_VULAB,
    CVEData,
    CVSSData,
    ASDData,
)


class TestCVEData:
    """测试CVEData模型"""

    def test_valid_cve_data_creation(self):
        """测试有效CVE数据创建"""
        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test vulnerability description",
            cve_desc_zh="测试漏洞描述",
            cve_cvssv3_score=7.5,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cve_cvssv3_severity="High",
            cwe=["CWE-79"],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        assert cve_data.cve_id == "CVE-2019-1234"
        assert cve_data.cve_cvssv3_score == 7.5
        assert cve_data.cve_cvssv3_severity == "High"

    def test_invalid_cve_id_format(self):
        """测试无效的CVE ID格式"""
        with pytest.raises(ValidationError):
            CVEData(
                cve_id="INVALID-ID",
                cve_desc="Test",
                cve_desc_zh="测试",
                cve_cvssv3_score=5.0,
                cve_cvssv3_vector="",
                cve_cvssv3_severity="Medium",
                cwe=[],
                affect_pack_info=[],
                reference_data=[],
                patch=[],
            )

    def test_cve_data_with_all_fields(self):
        """测试带所有字段的CVE数据"""
        cve_data = CVEData(
            cve_id="CVE-2024-47685",
            cve_desc="A vulnerability in the Linux kernel",
            cve_desc_zh="Linux内核中的一个漏洞",
            cve_cvssv3_score=9.8,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
            cve_cvssv3_severity="Critical",
            cwe=["CWE-416", "CWE-787"],
            affect_pack_info=[{"package": "linux", "version": "5.15"}],
            reference_data=[{"url": "https://example.com/advisory"}],
            patch=[{"url": "https://git.kernel.org/patch"}],
        )

        assert len(cve_data.cwe) == 2
        assert len(cve_data.affect_pack_info) == 1
        assert len(cve_data.reference_data) == 1
        assert len(cve_data.patch) == 1


class TestCVSSData:
    """测试CVSSData模型"""

    def test_cvss_data_creation(self):
        """测试CVSS数据创建"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=7.5,
            cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cvssv3_severity="High",
            cve_from=LABEL_VULAB,
        )

        assert cvss_data.cve_id == "CVE-2019-1234"
        assert cvss_data.cvssv3_score == 7.5
        assert cvss_data.cve_from == LABEL_VULAB

    def test_cvss_data_with_redhat_source(self):
        """测试来自RedHat的CVSS数据"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=6.5,
            cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
            cvssv3_severity="Medium",
            cve_from=LABEL_REDHAT,
        )

        assert cvss_data.cve_from == LABEL_REDHAT

    def test_cvss_data_with_optional_fields(self):
        """测试带可选字段的CVSS数据"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=None,
            cvssv3_vector=None,
            cvssv3_severity=None,
            cve_from=None,
        )

        assert cvss_data.cvssv3_score is None
        assert cvss_data.cvssv3_vector is None
        assert cvss_data.cve_from is None


class TestASDData:
    """测试ASDData模型"""

    def test_asd_data_creation(self):
        """测试ASD数据创建"""
        asd_data = ASDData(
            cve_id="CVE-2019-1234",
            attack_behaviors=[],
            mitre_techniques=[],
            consistency_verified=True,
            scenario_description="攻击场景描述",
            kill_chain="",
        )

        assert asd_data.cve_id == "CVE-2019-1234"
        assert asd_data.consistency_verified is True
        assert asd_data.scenario_description == "攻击场景描述"

    def test_asd_data_default_values(self):
        """测试ASD数据默认值"""
        asd_data = ASDData(cve_id="CVE-2019-1234")

        assert asd_data.attack_behaviors == []
        assert asd_data.mitre_techniques == []
        assert asd_data.consistency_verified is False
        assert asd_data.scenario_description == ""
        assert asd_data.kill_chain == ""


class TestCVSSVectorAgentState:
    """测试CVSSVectorAgent状态"""

    def test_state_with_cve_data(self):
        """测试带CVE数据的状态"""
        from langchain_core.messages import HumanMessage

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test vulnerability",
            cve_desc_zh="测试漏洞",
            cve_cvssv3_score=7.5,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cve_cvssv3_severity="High",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        # 模拟状态字典
        state = {
            "messages": [HumanMessage(content="CVE-2019-1234")],
            "cve_data": cve_data,
            "cvss_data": [],
            "cvss_vector": "",
            "vulab_score": 0.0,
            "vulab_severity": "Moderate",
            "statement_vector": "",
            "statement": "",
            "asd_data": "",
            "x_sec_cvss_vector": {},
        }

        assert state["cve_data"].cve_id == "CVE-2019-1234"
        assert len(state["messages"]) == 1


class TestCVSSVectorAgentNodeFunctions:
    """测试CVSSVectorAgent节点函数"""

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_is_cve_data_empty_with_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVE数据非空时的条件分支"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent
        from langchain_core.messages import HumanMessage

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test",
            cve_desc_zh="测试",
            cve_cvssv3_score=5.0,
            cve_cvssv3_vector="",
            cve_cvssv3_severity="Medium",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        state = {"cve_data": cve_data}
        result = agent._is_cve_data_empty(state)

        assert result == "generate_asd_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_is_cve_data_empty_without_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVE数据为空时的条件分支（应返回barrier节点）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {"cve_data": None}
        result = agent._is_cve_data_empty(state)

        # 使用barrier节点后，空数据时应该返回cve_path_barrier
        assert result == "cve_path_barrier"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_data_check_with_redhat_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVSS数据检查（有RedHat数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=7.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="High",
                cve_from=LABEL_VULAB,
            ),
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=6.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="Medium",
                cve_from=LABEL_REDHAT,
            ),
        ]

        state = {"cvss_data": cvss_data}
        result = agent._cvss_data_check(state)

        # 有RedHat数据时应该去获取额外的statement数据
        assert result == "get_cvss_statement_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_data_check_empty(self, mock_llm, mock_asd_agent, mock_data_agent):
        """测试CVSS数据检查（数据为空）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {"cvss_data": None}
        result = agent._cvss_data_check(state)

        assert result == "normalize_cvss_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_vector_check_single_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVSS向量检查（单条数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=7.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="High",
                cve_from=LABEL_VULAB,
            )
        ]

        state = {"cvss_data": cvss_data, "cvss_vector": ""}
        result = agent._cvss_vector_check(state)

        # 单条数据直接normalize
        assert result == "normalize_cvss_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_valid(self, mock_llm, mock_asd_agent, mock_data_agent):
        """测试解析有效JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试普通JSON
        json_content = '{"cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", "score": 7.5}'
        result = agent._parse_json_response(json_content)

        assert result["cvss_vector"] == "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
        assert result["score"] == 7.5

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_with_markdown(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试解析带Markdown标记的JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试带markdown标记的JSON
        json_content = '```json\n{"cvss_vector": "test", "score": 5.5}\n```'
        result = agent._parse_json_response(json_content)

        assert result["cvss_vector"] == "test"
        assert result["score"] == 5.5

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_invalid(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试解析无效JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试无效JSON
        invalid_content = "This is not valid JSON"
        result = agent._parse_json_response(invalid_content)

        assert result == {}


class TestCVSSVectorAgentNormalizeData:
    """测试CVSS数据归一化"""

    @patch("core.agents.mimora.cvss_vector_agent.get_db")
    @patch("core.agents.mimora.cvss_vector_agent.get_settings")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_normalize_cvss_data_with_vulab(
        self,
        mock_llm,
        mock_asd_agent,
        mock_data_agent,
        mock_settings,
        mock_get_db,
    ):
        """测试归一化CVSS数据（有VULAB数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock数据库
        mock_db = MagicMock()
        mock_db.get_cve_clean_result_by_cve_id.return_value = []
        mock_get_db.return_value = mock_db

        agent = CVSSVectorAgent()

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test",
            cve_desc_zh="测试",
            cve_cvssv3_score=5.0,
            cve_cvssv3_vector="CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
            cve_cvssv3_severity="Medium",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=5.5,
                cvssv3_vector="CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                cvssv3_severity="Medium",
                cve_from=LABEL_VULAB,
            )
        ]

        state = {
            "cve_data": cve_data,
            "cvss_data": cvss_data,
            "asd_data": "",
        }

        result = agent._normalize_cvss_data(state)

        assert result["vulab_score"] == 5.5
        assert result["vulab_severity"] == "Medium"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_normalize_cvss_data_no_cve_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试归一化CVSS数据（无CVE数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {
            "cve_data": None,
            "cvss_data": [],
            "asd_data": "",
        }

        result = agent._normalize_cvss_data(state)

        assert result["cve_data"] is None
        assert result["vulab_score"] == 0.0
        assert result["vulab_severity"] == "Moderate"
        assert "error_message" in result


class TestCVSSVectorAgentRun:
    """测试CVSSVectorAgent运行"""

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_returns_correct_format(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行返回正确格式"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 7.5,
            "vulab_severity": "High",
            "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N",
                "score": 6.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 验证返回格式
        assert "vulab" in result
        assert "mimora" in result
        assert "default" in result

        # 验证vulab结果
        assert "cvss_vector" in result["vulab"]
        assert "score" in result["vulab"]
        assert "severity" in result["vulab"]

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_default_selection_equal_scores(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行默认选择（分数相等）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果（分数相等）
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 5.5,
            "vulab_severity": "Medium",
            "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                "score": 5.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 分数相等时选择mimora
        assert result["default"] == "mimora"

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_default_selection_large_difference(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行默认选择（分数相差大于3）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果（分数相差大于3）
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 9.8,
            "vulab_severity": "Critical",
            "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                "score": 5.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 分数相差大于3时选择vulab
        assert result["default"] == "vulab"


class TestCVECVSSAnalysisFunction:
    """测试cve_cvss_analysis全局函数"""

    @patch("core.agents.mimora.cvss_vector_agent._CVSS_VECTOR_AGENT")
    def test_cve_cvss_analysis_success(self, mock_agent):
        """测试成功执行CVE CVSS分析"""
        from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis

        # Mock agent运行结果
        mock_agent.run.return_value = {
            "vulab": {"cvss_vector": "", "score": 5.5, "severity": "Medium"},
            "mimora": {"cvss_vector": "", "score": 5.5, "cvss_severity": "Medium"},
            "default": "mimora",
        }

        result_str = cve_cvss_analysis("CVE-2019-1234")
        result = json.loads(result_str)

        assert "vulab" in result
        assert "mimora" in result
        assert "default" in result

    @patch("core.agents.mimora.cvss_vector_agent._CVSS_VECTOR_AGENT")
    def test_cve_cvss_analysis_exception(self, mock_agent):
        """测试CVE CVSS分析异常"""
        from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis

        # Mock agent运行异常
        mock_agent.run.side_effect = Exception("Analysis failed")

        result_str = cve_cvss_analysis("CVE-2019-1234")
        result = json.loads(result_str)

        # 异常时返回空字典
        assert result == {}


class TestLabelConstants:
    """测试标签常量"""

    def test_label_redhat(self):
        """测试RedHat标签"""
        assert LABEL_REDHAT == "secalert@redhat.com"

    def test_label_vulab(self):
        """测试VULAB标签"""
        assert LABEL_VULAB == "VULAB"


class TestKeepNonEmptyStrReducer:
    """测试_keep_non_empty_str_reducer函数

    该reducer用于处理LangGraph并发执行时多个节点同时更新同一字段的冲突。
    策略：保留有内容的值，确保有效数据不会被空值覆盖。
    """

    def test_reducer_both_empty(self):
        """测试current和new都为空时返回空字符串"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("", "")
        assert result == ""

    def test_reducer_current_empty_new_has_content(self):
        """测试current为空、new有内容时返回new"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("", "实际的ASD数据")
        assert result == "实际的ASD数据"

    def test_reducer_current_has_content_new_empty(self):
        """测试current有内容、new为空时返回current"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("实际的ASD数据", "")
        assert result == "实际的ASD数据"

    def test_reducer_both_have_content(self):
        """测试current和new都有内容时返回current（优先保留已有数据）"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("数据A", "数据B")
        assert result == "数据A"

    def test_reducer_current_whitespace_only(self):
        """测试current只有空白字符时返回new"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("   ", "有效数据")
        assert result == "有效数据"

    def test_reducer_new_whitespace_only(self):
        """测试new只有空白字符时返回current"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("有效数据", "   ")
        assert result == "有效数据"

    def test_reducer_both_whitespace_only(self):
        """测试current和new都只有空白字符时返回空字符串"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("  \t\n  ", "   \n  ")
        assert result == ""

    def test_reducer_simulates_asd_data_concurrent_update(self):
        """模拟asd_data并发更新场景：generate_asd_data有数据，normalize_cvss_data返回空"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        # 场景1：generate_asd_data先写入，normalize_cvss_data后写入空值
        result = _keep_non_empty_str_reducer("攻击场景：通过网络远程利用漏洞...", "")
        assert result == "攻击场景：通过网络远程利用漏洞..."

        # 场景2：顺序相反，normalize_cvss_data先写入空值，generate_asd_data后写入
        result = _keep_non_empty_str_reducer("", "攻击场景：通过网络远程利用漏洞...")
        assert result == "攻击场景：通过网络远程利用漏洞..."

    def test_reducer_simulates_statement_concurrent_update(self):
        """模拟statement并发更新场景：get_cvss_statement_data有数据，normalize_cvss_data返回空"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        # 场景1：get_cvss_statement_data先写入，normalize_cvss_data后写入空值
        statement = "This vulnerability affects the product..."
        result = _keep_non_empty_str_reducer(statement, "")
        assert result == statement

        # 场景2：顺序相反
        result = _keep_non_empty_str_reducer("", statement)
        assert result == statement


def run_unit_tests():
    """运行单元测试"""
    print("🚀 开始CVSS Vector Agent单元测试...")

    # 使用pytest运行测试
    result = pytest.main([__file__, "-v", "--tb=short"])

    if result == 0:
        print("✅ CVSS Vector Agent单元测试全部通过!")
    else:
        print("❌ CVSS Vector Agent单元测试失败!")

    return result == 0


if __name__ == "__main__":
    run_unit_tests()

```

```python
"""
CVSS Vector Agent 单元测试
专注于测试CVSSVectorAgent的各个节点函数、状态管理、数据处理等
"""

import json
import os
import sys
from unittest.mock import MagicMock, patch, PropertyMock

import pytest
from pydantic import ValidationError

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))

from core.agents.mimora.data_type import (
    LABEL_REDHAT,
    LABEL_VULAB,
    CVEData,
    CVSSData,
    ASDData,
)


class TestCVEData:
    """测试CVEData模型"""

    def test_valid_cve_data_creation(self):
        """测试有效CVE数据创建"""
        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test vulnerability description",
            cve_desc_zh="测试漏洞描述",
            cve_cvssv3_score=7.5,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cve_cvssv3_severity="High",
            cwe=["CWE-79"],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        assert cve_data.cve_id == "CVE-2019-1234"
        assert cve_data.cve_cvssv3_score == 7.5
        assert cve_data.cve_cvssv3_severity == "High"

    def test_invalid_cve_id_format(self):
        """测试无效的CVE ID格式"""
        with pytest.raises(ValidationError):
            CVEData(
                cve_id="INVALID-ID",
                cve_desc="Test",
                cve_desc_zh="测试",
                cve_cvssv3_score=5.0,
                cve_cvssv3_vector="",
                cve_cvssv3_severity="Medium",
                cwe=[],
                affect_pack_info=[],
                reference_data=[],
                patch=[],
            )

    def test_cve_data_with_all_fields(self):
        """测试带所有字段的CVE数据"""
        cve_data = CVEData(
            cve_id="CVE-2024-47685",
            cve_desc="A vulnerability in the Linux kernel",
            cve_desc_zh="Linux内核中的一个漏洞",
            cve_cvssv3_score=9.8,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
            cve_cvssv3_severity="Critical",
            cwe=["CWE-416", "CWE-787"],
            affect_pack_info=[{"package": "linux", "version": "5.15"}],
            reference_data=[{"url": "https://example.com/advisory"}],
            patch=[{"url": "https://git.kernel.org/patch"}],
        )

        assert len(cve_data.cwe) == 2
        assert len(cve_data.affect_pack_info) == 1
        assert len(cve_data.reference_data) == 1
        assert len(cve_data.patch) == 1


class TestCVSSData:
    """测试CVSSData模型"""

    def test_cvss_data_creation(self):
        """测试CVSS数据创建"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=7.5,
            cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cvssv3_severity="High",
            cve_from=LABEL_VULAB,
        )

        assert cvss_data.cve_id == "CVE-2019-1234"
        assert cvss_data.cvssv3_score == 7.5
        assert cvss_data.cve_from == LABEL_VULAB

    def test_cvss_data_with_redhat_source(self):
        """测试来自RedHat的CVSS数据"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=6.5,
            cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
            cvssv3_severity="Medium",
            cve_from=LABEL_REDHAT,
        )

        assert cvss_data.cve_from == LABEL_REDHAT

    def test_cvss_data_with_optional_fields(self):
        """测试带可选字段的CVSS数据"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=None,
            cvssv3_vector=None,
            cvssv3_severity=None,
            cve_from=None,
        )

        assert cvss_data.cvssv3_score is None
        assert cvss_data.cvssv3_vector is None
        assert cvss_data.cve_from is None


class TestASDData:
    """测试ASDData模型"""

    def test_asd_data_creation(self):
        """测试ASD数据创建"""
        asd_data = ASDData(
            cve_id="CVE-2019-1234",
            attack_behaviors=[],
            mitre_techniques=[],
            consistency_verified=True,
            scenario_description="攻击场景描述",
            kill_chain="",
        )

        assert asd_data.cve_id == "CVE-2019-1234"
        assert asd_data.consistency_verified is True
        assert asd_data.scenario_description == "攻击场景描述"

    def test_asd_data_default_values(self):
        """测试ASD数据默认值"""
        asd_data = ASDData(cve_id="CVE-2019-1234")

        assert asd_data.attack_behaviors == []
        assert asd_data.mitre_techniques == []
        assert asd_data.consistency_verified is False
        assert asd_data.scenario_description == ""
        assert asd_data.kill_chain == ""


class TestCVSSVectorAgentState:
    """测试CVSSVectorAgent状态"""

    def test_state_with_cve_data(self):
        """测试带CVE数据的状态"""
        from langchain_core.messages import HumanMessage

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test vulnerability",
            cve_desc_zh="测试漏洞",
            cve_cvssv3_score=7.5,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cve_cvssv3_severity="High",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        # 模拟状态字典
        state = {
            "messages": [HumanMessage(content="CVE-2019-1234")],
            "cve_data": cve_data,
            "cvss_data": [],
            "cvss_vector": "",
            "vulab_score": 0.0,
            "vulab_severity": "Moderate",
            "statement_vector": "",
            "statement": "",
            "asd_data": "",
            "x_sec_cvss_vector": {},
        }

        assert state["cve_data"].cve_id == "CVE-2019-1234"
        assert len(state["messages"]) == 1


class TestCVSSVectorAgentNodeFunctions:
    """测试CVSSVectorAgent节点函数"""

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_is_cve_data_empty_with_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVE数据非空时的条件分支"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent
        from langchain_core.messages import HumanMessage

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test",
            cve_desc_zh="测试",
            cve_cvssv3_score=5.0,
            cve_cvssv3_vector="",
            cve_cvssv3_severity="Medium",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        state = {"cve_data": cve_data}
        result = agent._is_cve_data_empty(state)

        assert result == "generate_asd_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_is_cve_data_empty_without_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVE数据为空时的条件分支（应返回barrier节点）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {"cve_data": None}
        result = agent._is_cve_data_empty(state)

        # 使用barrier节点后，空数据时应该返回cve_path_barrier
        assert result == "cve_path_barrier"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_data_check_with_redhat_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVSS数据检查（有RedHat数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=7.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="High",
                cve_from=LABEL_VULAB,
            ),
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=6.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="Medium",
                cve_from=LABEL_REDHAT,
            ),
        ]

        state = {"cvss_data": cvss_data}
        result = agent._cvss_data_check(state)

        # 有RedHat数据时应该去获取额外的statement数据
        assert result == "get_cvss_statement_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_data_check_empty(self, mock_llm, mock_asd_agent, mock_data_agent):
        """测试CVSS数据检查（数据为空，应返回barrier节点）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {"cvss_data": None}
        result = agent._cvss_data_check(state)

        # 使用barrier节点后，空数据时应该返回cvss_path_barrier
        assert result == "cvss_path_barrier"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_vector_check_single_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVSS向量检查（单条数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=7.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="High",
                cve_from=LABEL_VULAB,
            )
        ]

        state = {"cvss_data": cvss_data, "cvss_vector": ""}
        result = agent._cvss_vector_check(state)

        # 单条数据直接normalize
        assert result == "normalize_cvss_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_valid(self, mock_llm, mock_asd_agent, mock_data_agent):
        """测试解析有效JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试普通JSON
        json_content = '{"cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", "score": 7.5}'
        result = agent._parse_json_response(json_content)

        assert result["cvss_vector"] == "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
        assert result["score"] == 7.5

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_with_markdown(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试解析带Markdown标记的JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试带markdown标记的JSON
        json_content = '```json\n{"cvss_vector": "test", "score": 5.5}\n```'
        result = agent._parse_json_response(json_content)

        assert result["cvss_vector"] == "test"
        assert result["score"] == 5.5

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_invalid(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试解析无效JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试无效JSON
        invalid_content = "This is not valid JSON"
        result = agent._parse_json_response(invalid_content)

        assert result == {}


class TestCVSSVectorAgentNormalizeData:
    """测试CVSS数据归一化"""

    @patch("core.agents.mimora.cvss_vector_agent.get_db")
    @patch("core.agents.mimora.cvss_vector_agent.get_settings")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_normalize_cvss_data_with_vulab(
        self,
        mock_llm,
        mock_asd_agent,
        mock_data_agent,
        mock_settings,
        mock_get_db,
    ):
        """测试归一化CVSS数据（有VULAB数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock数据库
        mock_db = MagicMock()
        mock_db.get_cve_clean_result_by_cve_id.return_value = []
        mock_get_db.return_value = mock_db

        agent = CVSSVectorAgent()

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test",
            cve_desc_zh="测试",
            cve_cvssv3_score=5.0,
            cve_cvssv3_vector="CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
            cve_cvssv3_severity="Medium",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=5.5,
                cvssv3_vector="CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                cvssv3_severity="Medium",
                cve_from=LABEL_VULAB,
            )
        ]

        state = {
            "cve_data": cve_data,
            "cvss_data": cvss_data,
            "asd_data": "",
        }

        result = agent._normalize_cvss_data(state)

        assert result["vulab_score"] == 5.5
        assert result["vulab_severity"] == "Medium"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_normalize_cvss_data_no_cve_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试归一化CVSS数据（无CVE数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {
            "cve_data": None,
            "cvss_data": [],
            "asd_data": "",
        }

        result = agent._normalize_cvss_data(state)

        assert result["cve_data"] is None
        assert result["vulab_score"] == 0.0
        assert result["vulab_severity"] == "Moderate"
        assert "error_message" in result


class TestCVSSVectorAgentRun:
    """测试CVSSVectorAgent运行"""

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_returns_correct_format(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行返回正确格式"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 7.5,
            "vulab_severity": "High",
            "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N",
                "score": 6.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 验证返回格式
        assert "vulab" in result
        assert "mimora" in result
        assert "default" in result

        # 验证vulab结果
        assert "cvss_vector" in result["vulab"]
        assert "score" in result["vulab"]
        assert "severity" in result["vulab"]

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_default_selection_equal_scores(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行默认选择（分数相等）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果（分数相等）
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 5.5,
            "vulab_severity": "Medium",
            "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                "score": 5.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 分数相等时选择mimora
        assert result["default"] == "mimora"

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_default_selection_large_difference(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行默认选择（分数相差大于3）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果（分数相差大于3）
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 9.8,
            "vulab_severity": "Critical",
            "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                "score": 5.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 分数相差大于3时选择vulab
        assert result["default"] == "vulab"


class TestCVECVSSAnalysisFunction:
    """测试cve_cvss_analysis全局函数"""

    @patch("core.agents.mimora.cvss_vector_agent._CVSS_VECTOR_AGENT")
    def test_cve_cvss_analysis_success(self, mock_agent):
        """测试成功执行CVE CVSS分析"""
        from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis

        # Mock agent运行结果
        mock_agent.run.return_value = {
            "vulab": {"cvss_vector": "", "score": 5.5, "severity": "Medium"},
            "mimora": {"cvss_vector": "", "score": 5.5, "cvss_severity": "Medium"},
            "default": "mimora",
        }

        result_str = cve_cvss_analysis("CVE-2019-1234")
        result = json.loads(result_str)

        assert "vulab" in result
        assert "mimora" in result
        assert "default" in result

    @patch("core.agents.mimora.cvss_vector_agent._CVSS_VECTOR_AGENT")
    def test_cve_cvss_analysis_exception(self, mock_agent):
        """测试CVE CVSS分析异常"""
        from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis

        # Mock agent运行异常
        mock_agent.run.side_effect = Exception("Analysis failed")

        result_str = cve_cvss_analysis("CVE-2019-1234")
        result = json.loads(result_str)

        # 异常时返回空字典
        assert result == {}


class TestLabelConstants:
    """测试标签常量"""

    def test_label_redhat(self):
        """测试RedHat标签"""
        assert LABEL_REDHAT == "secalert@redhat.com"

    def test_label_vulab(self):
        """测试VULAB标签"""
        assert LABEL_VULAB == "VULAB"


class TestKeepNonEmptyStrReducer:
    """测试_keep_non_empty_str_reducer函数

    该reducer用于处理LangGraph并发执行时多个节点同时更新同一字段的冲突。
    策略：保留有内容的值，确保有效数据不会被空值覆盖。
    """

    def test_reducer_both_empty(self):
        """测试current和new都为空时返回空字符串"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("", "")
        assert result == ""

    def test_reducer_current_empty_new_has_content(self):
        """测试current为空、new有内容时返回new"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("", "实际的ASD数据")
        assert result == "实际的ASD数据"

    def test_reducer_current_has_content_new_empty(self):
        """测试current有内容、new为空时返回current"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("实际的ASD数据", "")
        assert result == "实际的ASD数据"

    def test_reducer_both_have_content(self):
        """测试current和new都有内容时返回current（优先保留已有数据）"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("数据A", "数据B")
        assert result == "数据A"

    def test_reducer_current_whitespace_only(self):
        """测试current只有空白字符时返回new"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("   ", "有效数据")
        assert result == "有效数据"

    def test_reducer_new_whitespace_only(self):
        """测试new只有空白字符时返回current"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("有效数据", "   ")
        assert result == "有效数据"

    def test_reducer_both_whitespace_only(self):
        """测试current和new都只有空白字符时返回空字符串"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("  \t\n  ", "   \n  ")
        assert result == ""

    def test_reducer_simulates_asd_data_concurrent_update(self):
        """模拟asd_data并发更新场景：generate_asd_data有数据，normalize_cvss_data返回空"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        # 场景1：generate_asd_data先写入，normalize_cvss_data后写入空值
        result = _keep_non_empty_str_reducer("攻击场景：通过网络远程利用漏洞...", "")
        assert result == "攻击场景：通过网络远程利用漏洞..."

        # 场景2：顺序相反，normalize_cvss_data先写入空值，generate_asd_data后写入
        result = _keep_non_empty_str_reducer("", "攻击场景：通过网络远程利用漏洞...")
        assert result == "攻击场景：通过网络远程利用漏洞..."

    def test_reducer_simulates_statement_concurrent_update(self):
        """模拟statement并发更新场景：get_cvss_statement_data有数据，normalize_cvss_data返回空"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        # 场景1：get_cvss_statement_data先写入，normalize_cvss_data后写入空值
        statement = "This vulnerability affects the product..."
        result = _keep_non_empty_str_reducer(statement, "")
        assert result == statement

        # 场景2：顺序相反
        result = _keep_non_empty_str_reducer("", statement)
        assert result == statement


def run_unit_tests():
    """运行单元测试"""
    print("🚀 开始CVSS Vector Agent单元测试...")

    # 使用pytest运行测试
    result = pytest.main([__file__, "-v", "--tb=short"])

    if result == 0:
        print("✅ CVSS Vector Agent单元测试全部通过!")
    else:
        print("❌ CVSS Vector Agent单元测试失败!")

    return result == 0


if __name__ == "__main__":
    run_unit_tests()

```

```python
"""
CVSS Vector Agent 单元测试
专注于测试CVSSVectorAgent的各个节点函数、状态管理、数据处理等
"""

import json
import os
import sys
from unittest.mock import MagicMock, patch, PropertyMock

import pytest
from pydantic import ValidationError

# 添加项目根目录到Python路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))

from core.agents.mimora.data_type import (
    LABEL_REDHAT,
    LABEL_VULAB,
    CVEData,
    CVSSData,
    ASDData,
)


class TestCVEData:
    """测试CVEData模型"""

    def test_valid_cve_data_creation(self):
        """测试有效CVE数据创建"""
        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test vulnerability description",
            cve_desc_zh="测试漏洞描述",
            cve_cvssv3_score=7.5,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cve_cvssv3_severity="High",
            cwe=["CWE-79"],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        assert cve_data.cve_id == "CVE-2019-1234"
        assert cve_data.cve_cvssv3_score == 7.5
        assert cve_data.cve_cvssv3_severity == "High"

    def test_invalid_cve_id_format(self):
        """测试无效的CVE ID格式"""
        with pytest.raises(ValidationError):
            CVEData(
                cve_id="INVALID-ID",
                cve_desc="Test",
                cve_desc_zh="测试",
                cve_cvssv3_score=5.0,
                cve_cvssv3_vector="",
                cve_cvssv3_severity="Medium",
                cwe=[],
                affect_pack_info=[],
                reference_data=[],
                patch=[],
            )

    def test_cve_data_with_all_fields(self):
        """测试带所有字段的CVE数据"""
        cve_data = CVEData(
            cve_id="CVE-2024-47685",
            cve_desc="A vulnerability in the Linux kernel",
            cve_desc_zh="Linux内核中的一个漏洞",
            cve_cvssv3_score=9.8,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
            cve_cvssv3_severity="Critical",
            cwe=["CWE-416", "CWE-787"],
            affect_pack_info=[{"package": "linux", "version": "5.15"}],
            reference_data=[{"url": "https://example.com/advisory"}],
            patch=[{"url": "https://git.kernel.org/patch"}],
        )

        assert len(cve_data.cwe) == 2
        assert len(cve_data.affect_pack_info) == 1
        assert len(cve_data.reference_data) == 1
        assert len(cve_data.patch) == 1


class TestCVSSData:
    """测试CVSSData模型"""

    def test_cvss_data_creation(self):
        """测试CVSS数据创建"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=7.5,
            cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cvssv3_severity="High",
            cve_from=LABEL_VULAB,
        )

        assert cvss_data.cve_id == "CVE-2019-1234"
        assert cvss_data.cvssv3_score == 7.5
        assert cvss_data.cve_from == LABEL_VULAB

    def test_cvss_data_with_redhat_source(self):
        """测试来自RedHat的CVSS数据"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=6.5,
            cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
            cvssv3_severity="Medium",
            cve_from=LABEL_REDHAT,
        )

        assert cvss_data.cve_from == LABEL_REDHAT

    def test_cvss_data_with_optional_fields(self):
        """测试带可选字段的CVSS数据"""
        cvss_data = CVSSData(
            cve_id="CVE-2019-1234",
            cvssv3_score=None,
            cvssv3_vector=None,
            cvssv3_severity=None,
            cve_from=None,
        )

        assert cvss_data.cvssv3_score is None
        assert cvss_data.cvssv3_vector is None
        assert cvss_data.cve_from is None


class TestASDData:
    """测试ASDData模型"""

    def test_asd_data_creation(self):
        """测试ASD数据创建"""
        asd_data = ASDData(
            cve_id="CVE-2019-1234",
            attack_behaviors=[],
            mitre_techniques=[],
            consistency_verified=True,
            scenario_description="攻击场景描述",
            kill_chain="",
        )

        assert asd_data.cve_id == "CVE-2019-1234"
        assert asd_data.consistency_verified is True
        assert asd_data.scenario_description == "攻击场景描述"

    def test_asd_data_default_values(self):
        """测试ASD数据默认值"""
        asd_data = ASDData(cve_id="CVE-2019-1234")

        assert asd_data.attack_behaviors == []
        assert asd_data.mitre_techniques == []
        assert asd_data.consistency_verified is False
        assert asd_data.scenario_description == ""
        assert asd_data.kill_chain == ""


class TestCVSSVectorAgentState:
    """测试CVSSVectorAgent状态"""

    def test_state_with_cve_data(self):
        """测试带CVE数据的状态"""
        from langchain_core.messages import HumanMessage

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test vulnerability",
            cve_desc_zh="测试漏洞",
            cve_cvssv3_score=7.5,
            cve_cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            cve_cvssv3_severity="High",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        # 模拟状态字典
        state = {
            "messages": [HumanMessage(content="CVE-2019-1234")],
            "cve_data": cve_data,
            "cvss_data": [],
            "cvss_vector": "",
            "vulab_score": 0.0,
            "vulab_severity": "Moderate",
            "statement_vector": "",
            "statement": "",
            "asd_data": "",
            "x_sec_cvss_vector": {},
        }

        assert state["cve_data"].cve_id == "CVE-2019-1234"
        assert len(state["messages"]) == 1


class TestCVSSVectorAgentNodeFunctions:
    """测试CVSSVectorAgent节点函数"""

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_is_cve_data_empty_with_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVE数据非空时的条件分支"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent
        from langchain_core.messages import HumanMessage

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test",
            cve_desc_zh="测试",
            cve_cvssv3_score=5.0,
            cve_cvssv3_vector="",
            cve_cvssv3_severity="Medium",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        state = {"cve_data": cve_data}
        result = agent._is_cve_data_empty(state)

        assert result == "generate_asd_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_is_cve_data_empty_without_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVE数据为空时的条件分支（应返回barrier节点）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {"cve_data": None}
        result = agent._is_cve_data_empty(state)

        # 使用barrier节点后，空数据时应该返回cve_path_barrier
        assert result == "cve_path_barrier"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_data_check_with_redhat_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVSS数据检查（有RedHat数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=7.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="High",
                cve_from=LABEL_VULAB,
            ),
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=6.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="Medium",
                cve_from=LABEL_REDHAT,
            ),
        ]

        state = {"cvss_data": cvss_data}
        result = agent._cvss_data_check(state)

        # 有RedHat数据时应该去获取额外的statement数据
        assert result == "get_cvss_statement_data"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_data_check_empty(self, mock_llm, mock_asd_agent, mock_data_agent):
        """测试CVSS数据检查（数据为空，应返回barrier节点）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {"cvss_data": None}
        result = agent._cvss_data_check(state)

        # 使用barrier节点后，空数据时应该返回cvss_path_barrier
        assert result == "cvss_path_barrier"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_cvss_vector_check_single_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试CVSS向量检查（单条数据，应返回barrier节点）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=7.5,
                cvssv3_vector="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
                cvssv3_severity="High",
                cve_from=LABEL_VULAB,
            )
        ]

        state = {"cvss_data": cvss_data, "cvss_vector": ""}
        result = agent._cvss_vector_check(state)

        # 使用barrier节点后，单条数据时应该返回cvss_path_barrier
        assert result == "cvss_path_barrier"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_valid(self, mock_llm, mock_asd_agent, mock_data_agent):
        """测试解析有效JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试普通JSON
        json_content = '{"cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", "score": 7.5}'
        result = agent._parse_json_response(json_content)

        assert result["cvss_vector"] == "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N"
        assert result["score"] == 7.5

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_with_markdown(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试解析带Markdown标记的JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试带markdown标记的JSON
        json_content = '```json\n{"cvss_vector": "test", "score": 5.5}\n```'
        result = agent._parse_json_response(json_content)

        assert result["cvss_vector"] == "test"
        assert result["score"] == 5.5

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_parse_json_response_invalid(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试解析无效JSON响应"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        # 测试无效JSON
        invalid_content = "This is not valid JSON"
        result = agent._parse_json_response(invalid_content)

        assert result == {}


class TestCVSSVectorAgentNormalizeData:
    """测试CVSS数据归一化"""

    @patch("core.agents.mimora.cvss_vector_agent.get_db")
    @patch("core.agents.mimora.cvss_vector_agent.get_settings")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_normalize_cvss_data_with_vulab(
        self,
        mock_llm,
        mock_asd_agent,
        mock_data_agent,
        mock_settings,
        mock_get_db,
    ):
        """测试归一化CVSS数据（有VULAB数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock数据库
        mock_db = MagicMock()
        mock_db.get_cve_clean_result_by_cve_id.return_value = []
        mock_get_db.return_value = mock_db

        agent = CVSSVectorAgent()

        cve_data = CVEData(
            cve_id="CVE-2019-1234",
            cve_desc="Test",
            cve_desc_zh="测试",
            cve_cvssv3_score=5.0,
            cve_cvssv3_vector="CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
            cve_cvssv3_severity="Medium",
            cwe=[],
            affect_pack_info=[],
            reference_data=[],
            patch=[],
        )

        cvss_data = [
            CVSSData(
                cve_id="CVE-2019-1234",
                cvssv3_score=5.5,
                cvssv3_vector="CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                cvssv3_severity="Medium",
                cve_from=LABEL_VULAB,
            )
        ]

        state = {
            "cve_data": cve_data,
            "cvss_data": cvss_data,
            "asd_data": "",
        }

        result = agent._normalize_cvss_data(state)

        assert result["vulab_score"] == 5.5
        assert result["vulab_severity"] == "Medium"

    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_normalize_cvss_data_no_cve_data(
        self, mock_llm, mock_asd_agent, mock_data_agent
    ):
        """测试归一化CVSS数据（无CVE数据）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        agent = CVSSVectorAgent()

        state = {
            "cve_data": None,
            "cvss_data": [],
            "asd_data": "",
        }

        result = agent._normalize_cvss_data(state)

        assert result["cve_data"] is None
        assert result["vulab_score"] == 0.0
        assert result["vulab_severity"] == "Moderate"
        assert "error_message" in result


class TestCVSSVectorAgentRun:
    """测试CVSSVectorAgent运行"""

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_returns_correct_format(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行返回正确格式"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 7.5,
            "vulab_severity": "High",
            "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N",
                "score": 6.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 验证返回格式
        assert "vulab" in result
        assert "mimora" in result
        assert "default" in result

        # 验证vulab结果
        assert "cvss_vector" in result["vulab"]
        assert "score" in result["vulab"]
        assert "severity" in result["vulab"]

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_default_selection_equal_scores(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行默认选择（分数相等）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果（分数相等）
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 5.5,
            "vulab_severity": "Medium",
            "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                "score": 5.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 分数相等时选择mimora
        assert result["default"] == "mimora"

    @patch("core.agents.mimora.cvss_vector_agent.X_SEC_LLM")
    @patch("core.agents.mimora.cvss_vector_agent.CVEDataAgent")
    @patch("core.agents.mimora.cvss_vector_agent.MitreASDAgent")
    @patch("core.agents.mimora.cvss_vector_agent.ChatTongyi")
    def test_run_default_selection_large_difference(
        self, mock_llm, mock_asd_agent, mock_data_agent, mock_x_sec_llm
    ):
        """测试运行默认选择（分数相差大于3）"""
        from core.agents.mimora.cvss_vector_agent import CVSSVectorAgent

        # Mock依赖
        mock_data_agent.return_value.agent = MagicMock()
        mock_asd_agent.return_value = MagicMock()
        mock_llm.return_value = MagicMock()

        # Mock X_SEC_LLM返回结果（分数相差大于3）
        mock_x_sec_llm.invoke.return_value = {
            "vulab_score": 9.8,
            "vulab_severity": "Critical",
            "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
            "x_sec_cvss_vector": {
                "cvss_vector": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H",
                "score": 5.5,
                "cvss_severity": "Medium",
            },
        }

        agent = CVSSVectorAgent()
        result = agent.run("CVE-2019-1234")

        # 分数相差大于3时选择vulab
        assert result["default"] == "vulab"


class TestCVECVSSAnalysisFunction:
    """测试cve_cvss_analysis全局函数"""

    @patch("core.agents.mimora.cvss_vector_agent._CVSS_VECTOR_AGENT")
    def test_cve_cvss_analysis_success(self, mock_agent):
        """测试成功执行CVE CVSS分析"""
        from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis

        # Mock agent运行结果
        mock_agent.run.return_value = {
            "vulab": {"cvss_vector": "", "score": 5.5, "severity": "Medium"},
            "mimora": {"cvss_vector": "", "score": 5.5, "cvss_severity": "Medium"},
            "default": "mimora",
        }

        result_str = cve_cvss_analysis("CVE-2019-1234")
        result = json.loads(result_str)

        assert "vulab" in result
        assert "mimora" in result
        assert "default" in result

    @patch("core.agents.mimora.cvss_vector_agent._CVSS_VECTOR_AGENT")
    def test_cve_cvss_analysis_exception(self, mock_agent):
        """测试CVE CVSS分析异常"""
        from core.agents.mimora.cvss_vector_agent import cve_cvss_analysis

        # Mock agent运行异常
        mock_agent.run.side_effect = Exception("Analysis failed")

        result_str = cve_cvss_analysis("CVE-2019-1234")
        result = json.loads(result_str)

        # 异常时返回空字典
        assert result == {}


class TestLabelConstants:
    """测试标签常量"""

    def test_label_redhat(self):
        """测试RedHat标签"""
        assert LABEL_REDHAT == "secalert@redhat.com"

    def test_label_vulab(self):
        """测试VULAB标签"""
        assert LABEL_VULAB == "VULAB"


class TestKeepNonEmptyStrReducer:
    """测试_keep_non_empty_str_reducer函数

    该reducer用于处理LangGraph并发执行时多个节点同时更新同一字段的冲突。
    策略：保留有内容的值，确保有效数据不会被空值覆盖。
    """

    def test_reducer_both_empty(self):
        """测试current和new都为空时返回空字符串"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("", "")
        assert result == ""

    def test_reducer_current_empty_new_has_content(self):
        """测试current为空、new有内容时返回new"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("", "实际的ASD数据")
        assert result == "实际的ASD数据"

    def test_reducer_current_has_content_new_empty(self):
        """测试current有内容、new为空时返回current"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("实际的ASD数据", "")
        assert result == "实际的ASD数据"

    def test_reducer_both_have_content(self):
        """测试current和new都有内容时返回current（优先保留已有数据）"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("数据A", "数据B")
        assert result == "数据A"

    def test_reducer_current_whitespace_only(self):
        """测试current只有空白字符时返回new"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("   ", "有效数据")
        assert result == "有效数据"

    def test_reducer_new_whitespace_only(self):
        """测试new只有空白字符时返回current"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("有效数据", "   ")
        assert result == "有效数据"

    def test_reducer_both_whitespace_only(self):
        """测试current和new都只有空白字符时返回空字符串"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        result = _keep_non_empty_str_reducer("  \t\n  ", "   \n  ")
        assert result == ""

    def test_reducer_simulates_asd_data_concurrent_update(self):
        """模拟asd_data并发更新场景：generate_asd_data有数据，normalize_cvss_data返回空"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        # 场景1：generate_asd_data先写入，normalize_cvss_data后写入空值
        result = _keep_non_empty_str_reducer("攻击场景：通过网络远程利用漏洞...", "")
        assert result == "攻击场景：通过网络远程利用漏洞..."

        # 场景2：顺序相反，normalize_cvss_data先写入空值，generate_asd_data后写入
        result = _keep_non_empty_str_reducer("", "攻击场景：通过网络远程利用漏洞...")
        assert result == "攻击场景：通过网络远程利用漏洞..."

    def test_reducer_simulates_statement_concurrent_update(self):
        """模拟statement并发更新场景：get_cvss_statement_data有数据，normalize_cvss_data返回空"""
        from core.agents.mimora.cvss_vector_agent import _keep_non_empty_str_reducer

        # 场景1：get_cvss_statement_data先写入，normalize_cvss_data后写入空值
        statement = "This vulnerability affects the product..."
        result = _keep_non_empty_str_reducer(statement, "")
        assert result == statement

        # 场景2：顺序相反
        result = _keep_non_empty_str_reducer("", statement)
        assert result == statement


def run_unit_tests():
    """运行单元测试"""
    print("🚀 开始CVSS Vector Agent单元测试...")

    # 使用pytest运行测试
    result = pytest.main([__file__, "-v", "--tb=short"])

    if result == 0:
        print("✅ CVSS Vector Agent单元测试全部通过!")
    else:
        print("❌ CVSS Vector Agent单元测试失败!")

    return result == 0


if __name__ == "__main__":
    run_unit_tests()

```


验证修改是否正确，查看更新后的测试用例：



## 修复完成

已修复三个失败的单元测试用例。原因与修改如下：

### 问题分析

使用 barrier 节点重构图结构后，条件边函数的返回值从 `"normalize_cvss_data"` 改为 barrier 节点名称：
- `_is_cve_data_empty()`: 空数据时返回 `"cve_path_barrier"`（之前是 `"normalize_cvss_data"`）
- `_cvss_data_check()`: 空数据时返回 `"cvss_path_barrier"`（之前是 `"normalize_cvss_data"`）
- `_cvss_vector_check()`: 单条数据时返回 `"cvss_path_barrier"`（之前是 `"normalize_cvss_data"`）

### 修复内容

更新了三个测试用例的期望值：

1. `test_is_cve_data_empty_without_data` (line 237-254)
   - 期望值：`"normalize_cvss_data"` → `"cve_path_barrier"`
   - 注释：更新为反映新的 barrier 节点行为

2. `test_cvss_data_check_empty` (line 298-313)
   - 期望值：`"normalize_cvss_data"` → `"cvss_path_barrier"`
   - 注释：更新为反映新的 barrier 节点行为

3. `test_cvss_vector_check_single_data` (line 318-344)
   - 期望值：`"normalize_cvss_data"` → `"cvss_path_barrier"`
   - 注释：更新为反映新的 barrier 节点行为

### 验证

- 无语法错误
- 测试用例期望值与代码实现一致
- 注释已更新，说明新的 barrier 节点行为

现在可以重新运行单元测试，应该能通过。这些修改确保了测试用例与使用 barrier 节点同步路径的新图结构保持一致。

