惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
小众软件
小众软件
有赞技术团队
有赞技术团队
MyScale Blog
MyScale Blog
A
About on SuperTechFans
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
博客园_首页
T
Troy Hunt's Blog
I
InfoQ
M
MIT News - Artificial intelligence
aimingoo的专栏
aimingoo的专栏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Proofpoint News Feed
博客园 - 司徒正美
Cloudbric
Cloudbric
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Forbes - Security
Forbes - Security
D
Docker
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
博客园 - 聂微东
S
Security Affairs
Security Archives - TechRepublic
Security Archives - TechRepublic
WordPress大学
WordPress大学
N
News and Events Feed by Topic
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Heimdal Security Blog
大猫的无限游戏
大猫的无限游戏
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
雷峰网
雷峰网
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
A
Arctic Wolf
J
Java Code Geeks
F
Full Disclosure
L
Lohrmann on Cybersecurity
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Project Zero
Project Zero
GbyAI
GbyAI
B
Blog
爱范儿
爱范儿

博客园 - 沐子馨

什么是Agent? Agent Loop:从对话式 AI 到自主智能体的范式跃迁 基于 Neo4j 与 Milvus 的图RAG系统搭建指南 解决复杂推理难题:KG-RAG 在大模型中的应用 RAG 评估常用工具简介 RAG系统效果不好?一文看懂如何进行系统评估 写出让 AI “秒懂”的技能Skill 深入理解 RAG 中的格式化生成与函数调用 解锁 RAG 系统中的高级检索与重排序策略 掌握查询重构与智能路由的艺术 告别黑盒:手把手实现一个可解释、可调试的 Text2SQL 代理系统 告别简单向量搜索:RAG 中的高级查询构建与优化策略 深入理解 RAG 中的混合搜索策略 告别基础检索:掌握 RAG 中的句子窗口与递归路由策略 构建多模态检索系统:Milvus 部署、Schema 设计与混合检索实践 向量数据库原理与实战:从核心机制到 FAISS 应用 多模态向量嵌入:从文本到图像的语义统一与 RAG 应用 构建高效 RAG 的基石:深度解析 Embedding 模型原理与优化 All-in-RAG:解锁大模型“开卷考试”的终极能力 AI Agent 是如何思考的?一文读懂推理与决策引擎 终端革命:AI Agent 正在重新定义命令行 给企业装上“AI 大脑”:AgentSpace 如何从“手动检索”跨越到“智能决策”? Agentic 框架快速概览 AI 智能体交互如何带领它走出对话框,从屏幕像素迈向真实物理世界 高级提示词技巧如何带领大模型走出“一本正经胡说八道”的误区? 当 AI 学会“开疆拓土”:探索与发现模式如何从源头打破静态知识的桎梏,在陌生领域催生新洞见? 拒绝 AI “手忙脚乱”:优先级模式如何为智能体打造毫秒级的“任务调度官”? 构建生产级可信 Agent:评估与监控中的自动化审计与闭环优化机制 当 AI 学会“三思后言”:安全护栏如何从源头掐灭偏见、幻觉与恶意攻击? 拒绝“张口就来”:推理技术如何让 AI 像人类一样拆解复杂难题? 当家方知柴米贵:资源感知优化如何让 AI 智能体告别“算力浪费”? 开启多智能体互联时代:A2A 协议与 AI 协作网络的底层逻辑 为智能体装上“实时百科全书”:RAG 如何打破 AI 的知识边界? 构建负责任的AI:人类监督、干预与反馈闭环机制 当工具失效、网络中断:AI智能体的应急与自修复指南 赋予AI“北极星”:如何让智能体自主设定并追踪目标 打破“M×N”集成魔咒:MCP如何重塑AI应用架构 赋予模型“生命力”:大语言模型如何通过学习实现持续适应 赋予模型“长期记忆”:大语言模型的记忆管理机制 AI的“社会性”:深入理解多智能体协作机制 理解AI智能体的规划机制 理解大模型的工具使用 智能体的元认知:反思模式与自我优化机制 智能体并行化模式:提升复杂工作流性能的关键技术 超越线性执行:使用LLM实现智能体路由模式 解构复杂任务:深入理解提示词链(Prompt Chaining) 别再被 AI 术语唬住了!一文理清你需要了解的所有概念 不止是多写几句:谷歌如何用69页白皮书重塑提示工程 [转]长PDF文档的总结与评估策略 Context 工程:如何把正确的上下文喂给 AI 前端如何写出优秀的 AI Agent Skills mac安装python后command not found: pip keep-alive 原理剖析 如何优雅地在 React 中使用TypeScript,看这一篇就够了! react useContext React Context 详细介绍(状态共享、数据传递) 以用户为中心的性能指标【译】 element下拉框远程搜索debounce防抖控制 vue-router打开新窗口 propmise: allSettled()与all()的区别 使用 new Date() 在chrome、安卓和 IOS 中表现不同
解锁图数据建模的奥秘
沐子馨 · 2026-07-02 · via 博客园 - 沐子馨

一、数据来源与转换

1.1 从Markdown到图数据的转换

本文的图数据来源于前面使用的Markdown格式菜谱数据。为了构建知识图谱,用AI开发了一个简单的Agent,通过LLM将结构化的Markdown菜谱数据转换为CSV格式的图数据。

转换流程:

  1. 读取Markdown菜谱:从数据源加载菜谱文件
  2. LLM解析提取:使用大语言模型识别和提取实体及关系
  3. 结构化输出:生成nodes.csv和relationships.csv文件
  4. 图数据导入:通过Cypher脚本导入Neo4j数据库

1.2 图数据文件结构

转换后的图数据包含两个核心文件:

data/C9/cypher/
├── nodes.csv          # 节点数据(菜谱、食材、步骤等)
├── relationships.csv  # 关系数据(菜谱-食材、菜谱-步骤等)
└── neo4j_import.cypher # 数据导入脚本

二、图数据模型设计

2.1 实际数据结构分析

基于LLM转换后的实际图数据,知识图谱包含以下核心实体类型。如果你有游戏逆向经验,可以把这些实体类型想象成虚幻引擎烹饪游戏中的对象类,节点间的关系就像对象间的指针引用:

核心实体类型:

  • **Recipe (菜谱)**:具体的菜品,包含难度、菜系、时间等属性
  • **Ingredient (食材)**:制作菜品所需的原料,包含分类、用量、单位等
  • **CookingStep (烹饪步骤)**:详细的制作步骤,包含方法、工具、时间估计
  • **CookingMethod (烹饪方法)**:如炒、煮、蒸、炸等烹饪技法
  • **CookingTool (烹饪工具)**:如炒锅、蒸锅、刀具等
  • **DifficultyLevel (难度等级)**:一星到五星的难度分级
  • **RecipeCategory (菜谱分类)**:素菜、荤菜、水产、早餐等分类

实际数据特点:

  • 统一编码体系:使用nodeId进行唯一标识(如201000001)
  • 多语言支持:包含preferredTerm、fsn等多语言字段
  • 丰富属性:每个实体包含详细的属性信息
  • 层次化结构:从抽象概念到具体实例的层次化组织

2.2 实际节点模型

基于实际数据的图数据模型:

image

  • 🍽️ Recipe (菜谱节点): 核心实体,包含菜谱的完整信息
  • 🥬 Ingredient (食材节点): 制作菜谱所需的食材信息
  • 👨‍🍳 CookingStep (烹饪步骤节点): 详细的制作步骤和方法
  • 📂 RecipeCategory (菜谱分类节点): 菜品分类(素菜、荤菜、水产等)
  • ⭐ DifficultyLevel (难度等级节点): 制作难度分级(一星到五星)

2.3 实际关系模型

基于实际数据的关系结构:

image

关系类型说明:

关系编码关系类型说明属性
801000001 REQUIRES 菜谱-食材关系 relationshipId, amount, unit
801000003 CONTAINS_STEP 菜谱-步骤关系 relationshipId, step_order
801000004 HAS_DIFFICULTY_LEVEL 菜谱-难度关系 relationshipId
801000005 BELONGS_TO_CATEGORY 菜谱-分类关系 relationshipId

关系特点:

  • 虚线箭头:表示带有丰富属性的关系(如REQUIRES、CONTAINS_STEP)
  • 实线箭头:表示简单的分类关系
  • 层次化结构:Root节点作为概念层次的顶层节点

三、Neo4j数据导入

3.1 数据准备脚本

系统通过 GraphDataPreparationModule 来处理图数据的加载和管理:

class GraphDataPreparationModule:
    def __init__(self, neo4j_config: dict):
        """
        初始化图数据准备模块
        
        Args:
            neo4j_config: Neo4j连接配置
        """
        self.driver = GraphDatabase.driver(
            neo4j_config['uri'],
            auth=(neo4j_config['user'], neo4j_config['password'])
        )
        
    def load_graph_data(self) -> List[Dict]:
        """
        从Neo4j加载图数据
        
        Returns:
            包含菜谱信息的字典列表
        """
        query = """
        MATCH (r:Recipe)
        OPTIONAL MATCH (r)-[:REQUIRES]->(i:Ingredient)
        OPTIONAL MATCH (r)-[:HAS_STEP]->(s:Step)
        OPTIONAL MATCH (r)-[:BELONGS_TO]->(c:Category)
        RETURN r, collect(DISTINCT i) as ingredients, 
               collect(DISTINCT s) as steps,
               collect(DISTINCT c) as categories
        ORDER BY r.name
        """
        
        with self.driver.session() as session:
            result = session.run(query)
            return [record for record in result]

3.2 实际CSV数据格式

转换后的CSV文件格式(基于实际数据):

# nodes.csv结构:
nodeId,labels,name,preferredTerm,fsn,conceptType,synonyms,category,difficulty,cuisineType,prepTime,cookTime,servings,tags,filePath,amount,unit,isMain,description,stepNumber,methods,tools,timeEstimate

# 实际数据示例:
201000184,Recipe,干煎阿根廷红虾,干煎阿根廷红虾,,Recipe,"[{'term': '干pan-fried阿根廷红虾', 'language': 'zh'}]",水产,3.0,,提前1天冷藏解冻+10分钟,约5分钟,1人,"趁热吃,柠檬可增酸提味",dishes\aquatic\干煎阿根廷红虾\干煎阿根廷红虾.md,,,,,,,,
201000185,Ingredient,阿根廷红虾,阿根廷红虾,,Ingredient,,蛋白质,,,,,,,,2-3,只,True,,,,,
201000196,CookingStep,步骤1,步骤1,,CookingStep,,,,,,,,,,,,,阿根廷红虾提前1天从速冻取出放到冷藏里自然解冻,1.0,解冻,冰箱,24小时

# relationships.csv结构:
startNodeId,endNodeId,relationshipType,relationshipId,amount,unit,step_order

# 实际关系示例:
201000184,201000185,801000001,R_000001,2-3,只,
201000184,201000196,801000003,R_000010,,,1.0
201000184,720000000,801000002,R_000020,,,

四、图数据查询与检索

4.1 基础查询模式

简单实体查询

// 查找所有水产类菜谱
MATCH (r:Recipe)
WHERE r.category = "水产"
RETURN r.name, r.difficulty, r.prepTime, r.cookTime

// 查找包含特定食材的菜谱
MATCH (r:Recipe)-[:REQUIRES]->(i:Ingredient)
WHERE i.name CONTAINS ""
RETURN r.name, r.difficulty, i.name, i.amount, i.unit

// 使用全文搜索查找菜谱
CALL db.index.fulltext.queryNodes("recipe_fulltext_index", "川菜 OR 辣椒")
YIELD node, score
RETURN node.name, node.category, score
ORDER BY score DESC

多跳关系查询

// 查找某个难度等级的所有菜谱(基于属性查询)
MATCH (r:Recipe)
WHERE r.difficulty = 3.0
RETURN r.name, r.category, r.prepTime, r.cookTime, r.difficulty

// 查找菜谱的完整制作流程
MATCH (r:Recipe {name: "干煎阿根廷红虾"})-[:CONTAINS_STEP]->(s:CookingStep)
RETURN r.name, s.stepNumber, s.description, s.methods, s.tools
ORDER BY s.stepNumber

4.2 复杂推理查询

基于约束的菜谱推荐

// 查找适合新手的简单菜谱(低难度、步骤少)
MATCH (r:Recipe)
WHERE r.difficulty <= 2.0
  AND r.stepCount <= 5
RETURN r.name, r.difficulty, r.stepCount, r.category
ORDER BY r.difficulty, r.stepCount

// 查找制作时间短的菜谱
MATCH (r:Recipe)
WHERE r.prepTime IS NOT NULL AND r.cookTime IS NOT NULL
  AND r.prepTime CONTAINS "分钟" AND r.cookTime CONTAINS "分钟"
RETURN r.name, r.prepTime, r.cookTime, r.category
ORDER BY r.name

菜谱组合推荐

// 查找同一分类下的不同菜谱
MATCH (r1:Recipe), (r2:Recipe)
WHERE r1.category = r2.category
  AND r1.category = "水产"
  AND r1.nodeId <> r2.nodeId
RETURN r1.name, r2.name, r1.category
LIMIT 5

// 查找包含相同食材的不同菜谱
MATCH (r1:Recipe)-[:REQUIRES]->(i:Ingredient)<-[:REQUIRES]-(r2:Recipe)
WHERE r1.nodeId <> r2.nodeId
  AND i.name = "阿根廷红虾"
RETURN r1.name, r2.name, i.name

五、图数据到文档的转换

5.1 结构化文档构建

def build_recipe_documents(self, graph_data: List[Dict]) -> List[Document]:
    """将图数据转换为结构化文档"""

    documents = []
    for record in graph_data:
        recipe = record['r']
        ingredients = record['ingredients']
        steps = record['steps']
        categories = record['categories']

        # 构建结构化文档内容
        content_parts = [
            f"# {recipe['name']}",
            f"分类: {', '.join([c['name'] for c in categories])}",
            f"难度: {recipe['difficulty']}星",
            # ... 时间、份量等基本信息
            "",
            "## 所需食材"
        ]

        # 添加食材列表
        for i, ingredient in enumerate(ingredients, 1):
            content_parts.append(f"{i}. {ingredient['name']}")

        content_parts.extend(["", "## 制作步骤"])

        # 添加制作步骤(按顺序排序)
        sorted_steps = sorted(steps, key=lambda x: x.get('order', 0))
        for step in sorted_steps:
            content_parts.extend([
                f"### 第{step['order']}步",
                step['description'],
                ""
            ])

        # 创建Document对象
        document = Document(
            page_content="\n".join(content_parts),
            metadata={
                'recipe_name': recipe['name'],
                'node_id': recipe.get('nodeId'),  # 关键:保持与图节点的关联
                'difficulty': recipe.get('difficulty', 0),
                'categories': [c['name'] for c in categories],
                'ingredients': [i['name'] for i in ingredients]
                # ... 其他元数据
            }
        )
        documents.append(document)

    return documents

为什么不直接读取原始Markdown文件?

虽然中HowToCook项目的Markdown格式是统一的,但图RAG的价值在于提供更丰富的信息:

原始Markdown的特点:

  • 格式统一:HowToCook项目有良好的Markdown结构(######层级)
  • 信息完整:包含菜品名称、原料、制作步骤等基本信息
  • 元数据推断:可以从文件路径推断分类,从★★★★★符号推断难度

图数据构建文档的额外价值:

  1. 关系信息丰富:包含食材间的替代关系、菜谱间的相似性等图关系
  2. 结构化查询:可以通过图关系快速获取相关信息(如"包含鸡肉的所有菜谱")
  3. 动态内容生成:根据图关系动态生成推荐内容(如"相似菜谱"、"替代食材")
  4. 语义增强:图数据库可以存储更丰富的语义信息和计算结果
  5. 查询优化:图查询在复杂关系检索上比文本搜索更高效

5.2 图RAG中的分块策略

在图RAG系统中,分块策略与之前项目有所不同,主要体现在数据来源和上下文获取方式的差异:

图RAG vs 传统RAG的分块对比:

特性传统RAG图RAG
数据来源 直接读取Markdown文件 从图数据库构建文档
上下文获取 父子文档映射 图关系遍历
关系信息 有限(仅父子关系) 丰富(多种图关系)
分块策略 按Markdown标题分块 按语义+长度智能分块
元数据来源 文件路径+内容推断 图节点结构化数据

图RAG分块的特点:

  1. 保持图关联:每个chunk通过parent_id与图节点关联
  2. 语义优先分块:优先按章节分块,保持语义完整性
  3. 丰富的元数据:直接从图节点获取结构化信息
  4. 双重上下文:既有文本块关系,又有图关系信息

5.3 实际分块实现

在图RAG系统中,采用的实际分块策略:

def chunk_documents(self, chunk_size: int = 500, chunk_overlap: int = 50) -> List[Document]:
    """图RAG文档分块:结合图结构优势的智能分块策略"""

    chunks = []
    for doc in self.documents:
        content = doc.page_content

        if len(content) <= chunk_size:
            # 短文档:保持完整,避免破坏语义
            chunk = Document(
                page_content=content,
                metadata={
                    **doc.metadata,
                    "parent_id": doc.metadata["node_id"],  # 关键:保持与图节点的关联
                    "chunk_index": 0,
                    "doc_type": "chunk"
                }
            )
            chunks.append(chunk)
        else:
            # 长文档:智能分块策略
            sections = content.split('\n## ')

            if len(sections) <= 1:
                # 无章节结构:按长度分块(带重叠)
                total_chunks = (len(content) - 1) // (chunk_size - chunk_overlap) + 1
                for i in range(total_chunks):
                    start = i * (chunk_size - chunk_overlap)
                    end = min(start + chunk_size, len(content))
                    # ... 创建chunk,保持parent_id关联
            else:
                # 有章节结构:按语义分块(推荐)
                for i, section in enumerate(sections):
                    chunk_content = section if i == 0 else f"## {section}"
                    # ... 创建chunk,包含section_title信息

    return chunks

图RAG的分块策略在保持语义完整性的基础上,充分利用图数据库的结构化优势。与直接读取Markdown文件不同,这里从图数据库构建标准化文档,每个chunk通过parent_id与原始Recipe节点保持关联,既继承了传统的父子文档映射关系,又能通过图关系遍历获取更丰富的上下文信息。

在具体实现上,采用智能分块策略:短文档保持完整避免破坏语义,长文档优先按##标题进行章节分块,必要时才进行长度分块,同时为每个chunk提供丰富的元数据(如chunk_id、chunk_index、total_chunks等),确保后续处理的灵活性和可追溯性。