深入 LangGraph State:Reducer 是如何让状态“自动合并”的

目录

一、一个非常容易踩的坑

[二、为什么会有 Reducer?](#二、为什么会有 Reducer?)

(一)Reducer策略

[(二)后端开发视角理解 Reducer](#(二)后端开发视角理解 Reducer)

[1. SQL 类比](#1. SQL 类比)

[2. Java 类比](#2. Java 类比)

[三、Reducer 解决的核心问题](#三、Reducer 解决的核心问题)

当前值

历史记录

四、核心原理

[(一)Channel 抽象](#(一)Channel 抽象)

[1. LastValue](#1. LastValue)

[2. BinaryOperatorAggregate](#2. BinaryOperatorAggregate)

[(二)使用 Annotated 声明 Reducer](#(二)使用 Annotated 声明 Reducer)

[(三)自定义 Reducer](#(三)自定义 Reducer)

五、源码分析

(一)_get_channels(schema)

(二)LastValue:覆盖模式

[(三)BinaryOperatorAggregate:Reducer 模式](#(三)BinaryOperatorAggregate:Reducer 模式)

(四)完整更新流程

六、实战代码

(一)全量代码展示

(二)重点代码说明

[1. 场景一:默认覆盖模式](#1. 场景一:默认覆盖模式)

[2. 场景二:自动累加模式](#2. 场景二:自动累加模式)

[3. 场景三:自定义 Reduce合并 Dict](#3. 场景三:自定义 Reduce合并 Dict)

(三)运行结果

如何理解结果?

[1. 场景一:覆盖](#1. 场景一:覆盖)

[2. 场景二:累加](#2. 场景二:累加)

[3. 场景三:自定义 Reducer](#3. 场景三:自定义 Reducer)

七、常见坑与排查

[(一)坑 1:忘记添加 Reducer](#(一)坑 1:忘记添加 Reducer)

错误写法

正确写法

[(二)坑 2:Reducer 字段未初始化](#(二)坑 2:Reducer 字段未初始化)

直接报错信息

正确做法

[(三)坑 3:Reducer 有副作用](#(三)坑 3:Reducer 有副作用)

错误示例

正确示例

八、工程化思考与生产实践

(一)工程化思考简要

[1. 历史记录无限增长](#1. 历史记录无限增长)

[2. 初始化统一管理](#2. 初始化统一管理)

[3. Reducer 必须保持纯函数](#3. Reducer 必须保持纯函数)

[4. 调试难度提升](#4. 调试难度提升)

(二)生产级实践建议

[1. 统一初始化](#1. 统一初始化)

[2. 给历史字段加上限](#2. 给历史字段加上限)

[3. 对话场景优先使用 add_messages](#3. 对话场景优先使用 add_messages)

九、总结

下一篇预告


干货分享,感谢您的阅读!

本文是「LangGraph 实战」系列第 2 篇。上一篇我们已经跑通了最小直线图,并留下了一个关键问题:

节点返回的 dict,究竟是如何合并进全局 State 的?

这一篇我们就来揭开 ChannelReducer 的面纱------这是从 Demo 迈向真实 Agent 的关键一步。

一、一个非常容易踩的坑

我们照着上一篇的套路,兴冲冲地写了一个对话 Agent。

每个节点都会向 messages 列表追加一条消息,最后希望把完整对话返回出来。结果一运行,却发现:

明明经过了 3 个节点,最终 messages 里却只剩下最后一个节点写入的那条消息,前面的内容全都消失了。

开始排查:

  • Node 逻辑没问题
  • Edge 连接也没问题
  • State 定义看起来也正常

最后才发现,问题出在一个几乎没人第一次会注意到的地方:

LangGraph 默认的 State 合并策略是"覆盖(Overwrite)",而不是"追加(Append)"。

后一个节点返回的值,会直接替换前一个节点写入的值。这也是 LangGraph 中最常见、最隐蔽的坑之一。而解决它的关键,就是理解今天的主角:Reducer

二、为什么会有 Reducer?

(一)Reducer策略

在 Demo 01 中,我们已经掌握了 StateGraph 的基本结构。但当开始构建真实 Agent 时,很快就会遇到这样的问题:

多个节点都在更新同一个字段时,到底应该覆盖,还是累加?

例如:

复制代码
messages

这个字段显然希望保留整个对话历史。

而像:

复制代码
current_step
status

这类字段通常只关心最新状态。

因此:

  • 有些字段应该覆盖
  • 有些字段应该累加

LangGraph 并没有替你做统一决定,而是把这个权力交给开发者:

每个 State 字段都可以拥有独立的合并策略。

而这个策略,就是 Reducer。

(二)后端开发视角理解 Reducer

如果你有后端开发经验,可以这样理解:

1. SQL 类比

普通字段:

sql 复制代码
UPDATE task SET status = 'done'

覆盖旧值。

而消息历史更像:

sql 复制代码
INSERT INTO messages (...)

不断累积。

2. Java 类比

覆盖:

java 复制代码
map.put(key, value)

自定义合并:

java 复制代码
map.merge(
    key,
    value,
    (oldValue, newValue) -> oldValue + newValue
)

这里的:

复制代码
(oldValue, newValue) -> ...

本质上就是 Reducer。

三、Reducer 解决的核心问题

两种策略的区别如下:

策略 行为 适用场景 类比
覆盖(默认) 新值直接替换旧值 当前状态、步骤标记 map.put() / SQL UPDATE
累加(Reducer) 新旧值通过函数合并 消息历史、执行记录 map.merge() / SQL INSERT

判断标准其实非常简单:

这个字段表示"当前值",还是"历史记录"?

如果是:

当前值

只关心最新状态:

复制代码
status
current_step
current_node

使用默认覆盖即可。

历史记录

希望保留全部过程:

复制代码
messages
steps
tool_calls

使用 Reducer。

四、核心原理

(一)Channel 抽象

LangGraph 的状态管理底层建立在 Channel 机制之上。每一个 State 字段,都会对应一个 Channel。Channel 负责决定:

这个字段应该如何接收和更新数据。

主要有两种类型:

1. LastValue

只保留最新值。

复制代码
旧值 -> 新值

直接覆盖。

2. BinaryOperatorAggregate

通过 Reducer 合并:

复制代码
旧值 + 新值 -> 合并结果

(二)使用 Annotated 声明 Reducer

定义方式如下:

python 复制代码
from typing import Annotated, TypedDict
import operator

class MyState(TypedDict):
    current_step: str                              # → LastValue(覆盖)
    messages: Annotated[list[str], operator.add]   # → 累加器(追加)

其中:

python 复制代码
Annotated[类型, Reducer]

表示:

  • 第一个参数:字段类型
  • 第二个参数:Reducer 函数

(三)自定义 Reducer

除了:

python 复制代码
operator.add

你还可以使用任意函数。

要求签名统一为:

python 复制代码
(old, new) -> merged

例如:

python 复制代码
def merge_dicts(old: dict, new: dict) -> dict:
    return {
        **old,
        **new
    }

class State(TypedDict):
    metadata: Annotated[
        dict,
        merge_dicts
    ]

这样多个节点写入的 dict 就会自动合并。

五、源码分析

(一)_get_channels(schema)

当创建 StateGraph 时:

python 复制代码
StateGraph(...)

内部会调用:

python 复制代码
_get_channels()

解析 TypedDict 上的类型注解。

简化后的逻辑如下:

python 复制代码
# langgraph/graph/state.py(简化)
def _get_channels(schema):
    channels = {}
    for field_name, field_type in get_type_hints(schema, include_extras=True).items():
        if hasattr(field_type, '__metadata__'):
            # 有 Annotated → 取 metadata[0] 作为 Reducer
            reducer = field_type.__metadata__[0]
            channels[field_name] = BinaryOperatorAggregate(field_type.__origin__, reducer)
        else:
            # 无 Annotated → 默认 LastValue(覆盖)
            channels[field_name] = LastValue(field_type)
    return channels

逻辑非常直接:

复制代码
有 Annotated
    ↓
BinaryOperatorAggregate

没有 Annotated
    ↓
LastValue

(二)LastValue:覆盖模式

核心代码几乎只有一行:

python 复制代码
class LastValue(BaseChannel):
    def update(self, values):
        if values:
            self.value = values[-1]   # 直接覆盖

只取最后一个值。

(三)BinaryOperatorAggregate:Reducer 模式

python 复制代码
class BinaryOperatorAggregate(BaseChannel):
    def update(self, values):
        for value in values:
            if self.value is not None:
                self.value = self.operator(self.value, value)  # old, new → merged
            else:
                self.value = value

本质就是不断执行:

复制代码
merged = reducer(old, new)

(四)完整更新流程

假设节点返回:

复制代码
{
    "messages": ["新消息"],
    "status": "done"
}

更新过程如下:

复制代码
Node 返回结果
        ↓

messages
        ↓
BinaryOperatorAggregate
        ↓
operator.add(
    old,
    ["新消息"]
)
        ↓
旧列表 + 新列表


status
        ↓
LastValue
        ↓
直接覆盖


最终 State 更新完成

六、实战代码

(一)全量代码展示

下面通过三个场景演示不同的 State 合并策略。原始代码全量展示如下:

python 复制代码
"""Demo 02: State Management --- 状态定义与 Reducer 机制。

演示 LangGraph 的状态管理核心:
1. TypedDict 定义类型安全的 State
2. Reducer(Annotated)实现自动累加 vs 覆盖
3. 自定义 Reducer 函数
4. State 在节点间的传递和更新

运行方式:
    python stages/stage1_fundamentals/02_state_management/main.py
"""

from __future__ import annotations

import operator
import sys
from pathlib import Path
from typing import Annotated, TypedDict

from langgraph.graph import END, START, StateGraph

sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent))

from shared import get_logger, log_step

logger = get_logger("demo.02_state_mgmt")


# ============================================================
# 场景一:覆盖模式 vs 累加模式对比
# ============================================================
class OverwriteState(TypedDict):
    """无 Reducer 的 State --- 所有字段都是覆盖模式。"""

    value: str
    counter: int


class AccumulateState(TypedDict):
    """带 Reducer 的 State --- 列表自动累加,计数覆盖。"""

    messages: Annotated[list[str], operator.add]
    steps: Annotated[list[str], operator.add]
    current_node: str


def demo_overwrite():
    """演示覆盖模式:每个 Node 的返回值直接替换旧值。"""
    print("\n--- 场景一:覆盖模式(无 Reducer)---")

    def node_a(state: OverwriteState) -> dict:
        log_step(logger, "node_a", f"接收到 value='{state['value']}', counter={state['counter']}")
        return {"value": "来自 node_a", "counter": 10}

    def node_b(state: OverwriteState) -> dict:
        log_step(logger, "node_b", f"接收到 value='{state['value']}', counter={state['counter']}")
        return {"value": "来自 node_b", "counter": 20}

    graph = StateGraph(OverwriteState)
    graph.add_node("a", node_a)
    graph.add_node("b", node_b)
    graph.add_edge(START, "a")
    graph.add_edge("a", "b")
    graph.add_edge("b", END)
    app = graph.compile()

    result = app.invoke({"value": "初始值", "counter": 0})

    print(f"  最终 value   = '{result['value']}'   ← node_b 的值覆盖了 node_a")
    print(f"  最终 counter = {result['counter']}        ← node_b 的值覆盖了 node_a")
    return result


def demo_accumulate():
    """演示累加模式:带 Reducer 的字段自动合并。"""
    print("\n--- 场景二:累加模式(Annotated + operator.add)---")

    def collector_a(state: AccumulateState) -> dict:
        log_step(logger, "collector_a", "添加消息和步骤记录")
        return {
            "messages": ["collector_a: 第一条消息"],
            "steps": ["collector_a"],
            "current_node": "collector_a",
        }

    def collector_b(state: AccumulateState) -> dict:
        log_step(logger, "collector_b", f"当前已有 {len(state['messages'])} 条消息")
        return {
            "messages": ["collector_b: 第二条消息", "collector_b: 第三条消息"],
            "steps": ["collector_b"],
            "current_node": "collector_b",
        }

    def summarizer(state: AccumulateState) -> dict:
        log_step(logger, "summarizer", f"汇总 {len(state['messages'])} 条消息")
        summary = f"共收集 {len(state['messages'])} 条消息,经过节点: {' → '.join(state['steps'])}"
        return {
            "messages": [f"总结: {summary}"],
            "steps": ["summarizer"],
            "current_node": "summarizer",
        }

    graph = StateGraph(AccumulateState)
    graph.add_node("collector_a", collector_a)
    graph.add_node("collector_b", collector_b)
    graph.add_node("summarizer", summarizer)
    graph.add_edge(START, "collector_a")
    graph.add_edge("collector_a", "collector_b")
    graph.add_edge("collector_b", "summarizer")
    graph.add_edge("summarizer", END)
    app = graph.compile()

    result = app.invoke({
        "messages": [],
        "steps": [],
        "current_node": "",
    })

    print(f"  messages 数量 = {len(result['messages'])}(自动累加,不是覆盖!)")
    for i, msg in enumerate(result["messages"]):
        print(f"    [{i+1}] {msg}")
    print(f"  steps         = {' → '.join(result['steps'])}")
    print(f"  current_node  = '{result['current_node']}'(无 Reducer,被最后一个节点覆盖)")
    return result


# ============================================================
# 场景三:自定义 Reducer 函数
# ============================================================
def merge_dicts(left: dict, right: dict) -> dict:
    """自定义 Reducer: 合并字典,保留所有 key,新值覆盖旧值。"""
    return {**left, **right}


def dedup_list(left: list, right: list) -> list:
    """自定义 Reducer: 列表合并并去重。"""
    seen = set()
    result = []
    for item in left + right:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result


class CustomReducerState(TypedDict):
    """使用自定义 Reducer 的 State。"""

    metadata: Annotated[dict, merge_dicts]
    tags: Annotated[list[str], dedup_list]
    status: str


def demo_custom_reducer():
    """演示自定义 Reducer 函数。"""
    print("\n--- 场景三:自定义 Reducer ---")

    def tagger_a(state: CustomReducerState) -> dict:
        log_step(logger, "tagger_a", "添加标签和元数据")
        return {
            "metadata": {"source": "tagger_a", "priority": "high"},
            "tags": ["python", "langgraph", "ai"],
            "status": "tagging",
        }

    def tagger_b(state: CustomReducerState) -> dict:
        log_step(logger, "tagger_b", "添加更多标签和元数据")
        return {
            "metadata": {"author": "demo", "priority": "low"},
            "tags": ["langgraph", "agent", "demo"],
            "status": "done",
        }

    graph = StateGraph(CustomReducerState)
    graph.add_node("tagger_a", tagger_a)
    graph.add_node("tagger_b", tagger_b)
    graph.add_edge(START, "tagger_a")
    graph.add_edge("tagger_a", "tagger_b")
    graph.add_edge("tagger_b", END)
    app = graph.compile()

    result = app.invoke({"metadata": {}, "tags": [], "status": ""})

    print(f"  metadata = {result['metadata']}")
    print("    ↑ merge_dicts: source 来自 tagger_a, author 来自 tagger_b, priority 被 tagger_b 覆盖")
    print(f"  tags     = {result['tags']}")
    print("    ↑ dedup_list: 'langgraph' 只出现一次(去重)")
    print(f"  status   = '{result['status']}'(无 Reducer,直接覆盖)")
    return result


def run_demo() -> dict:
    """运行 State Management Demo。"""
    print("=" * 60)
    print("  Demo 02: State Management --- 状态定义与 Reducer 机制")
    print("=" * 60)

    result1 = demo_overwrite()
    result2 = demo_accumulate()
    result3 = demo_custom_reducer()

    print()
    print("=" * 60)
    print("  关键概念回顾")
    print("=" * 60)
    print("  1. 无 Reducer     : 字段直接覆盖(LastValue 策略)")
    print("  2. operator.add   : 列表自动拼接、数字自动相加")
    print("  3. 自定义 Reducer : 任意合并逻辑(dict 合并、列表去重等)")
    print("  4. Annotated      : 使用 typing.Annotated 声明 Reducer")
    print("  5. Channel        : LangGraph 内部的数据管道抽象")
    print()

    return {"overwrite": result1, "accumulate": result2, "custom": result3}


if __name__ == "__main__":
    run_demo()

(二)重点代码说明

1. 场景一:默认覆盖模式

python 复制代码
# 场景一:覆盖模式(无 Reducer)
class OverwriteState(TypedDict):
    value: str       # 每个节点写它都会覆盖
    counter: int

没有 Reducer。所有更新都会覆盖旧值。

2. 场景二:自动累加模式

python 复制代码
# 场景二:累加模式(operator.add)
class AccumulateState(TypedDict):
    messages: Annotated[list[str], operator.add]  # 自动追加
    steps: Annotated[list[str], operator.add]
    current_node: str                             # 无 Reducer → 仍覆盖

其中:

复制代码
messages
steps

自动追加。

而:

复制代码
current_node

仍然保持覆盖模式。

3. 场景三:自定义 Reduce合并 Dict

python 复制代码
# 场景三:自定义 Reducer
def merge_dicts(left: dict, right: dict) -> dict:
    return {**left, **right}

def dedup_list(left: list, right: list) -> list:
    seen, result = set(), []
    for item in left + right:
        if item not in seen:
            seen.add(item)
            result.append(item)
    return result

class CustomReducerState(TypedDict):
    metadata: Annotated[dict, merge_dicts]    # dict 合并
    tags: Annotated[list[str], dedup_list]    # 追加并去重
    status: str

(三)运行结果

实际执行输出如下:

python 复制代码
Connected to server 127.0.0.1:50252
============================================================
  Demo 02: State Management --- 状态定义与 Reducer 机制
============================================================

--- 场景一:覆盖模式(无 Reducer)---
  最终 value   = '来自 node_b'   ← node_b 的值覆盖了 node_a
  最终 counter = 20        ← node_b 的值覆盖了 node_a

--- 场景二:累加模式(Annotated + operator.add)---
  messages 数量 = 4(自动累加,不是覆盖!)
    [1] collector_a: 第一条消息
    [2] collector_b: 第二条消息
    [3] collector_b: 第三条消息
    [4] 总结: 共收集 3 条消息,经过节点: collector_a → collector_b
  steps         = collector_a → collector_b → summarizer
  current_node  = 'summarizer'(无 Reducer,被最后一个节点覆盖)

--- 场景三:自定义 Reducer ---
  metadata = {'source': 'tagger_a', 'priority': 'low', 'author': 'demo'}
    ↑ merge_dicts: source 来自 tagger_a, author 来自 tagger_b, priority 被 tagger_b 覆盖
  tags     = ['python', 'langgraph', 'ai', 'agent', 'demo']
    ↑ dedup_list: 'langgraph' 只出现一次(去重)
  status   = 'done'(无 Reducer,直接覆盖)

============================================================
  关键概念回顾
============================================================
  1. 无 Reducer     : 字段直接覆盖(LastValue 策略)
  2. operator.add   : 列表自动拼接、数字自动相加
  3. 自定义 Reducer : 任意合并逻辑(dict 合并、列表去重等)
  4. Annotated      : 使用 typing.Annotated 声明 Reducer
  5. Channel        : LangGraph 内部的数据管道抽象

2026-06-04 23:12:06 INFO     demo.02_state_mgmt   | 📌  接收到 value='初始值',  
                             counter=0                                          
                    INFO     demo.02_state_mgmt   | 📌  接收到 value='来自      
                             node_a', counter=10                                
                    INFO     demo.02_state_mgmt   | 📌  添加消息和步骤记录      
                    INFO     demo.02_state_mgmt   | 📌  当前已有 1 条消息       
                    INFO     demo.02_state_mgmt   | 📌  汇总 3 条消息           
                    INFO     demo.02_state_mgmt   | 📌  添加标签和元数据        
                    INFO     demo.02_state_mgmt   | 📌  添加更多标签和元数据    

Process finished with exit code 0

如何理解结果?

1. 场景一:覆盖
复制代码
value
counter

都只保留了最后一个节点的写入结果。这正是默认行为。

2. 场景二:累加
复制代码
messages

经过多个节点后保留了全部内容。

而:

复制代码
current_node

因为没有 Reducer,

最终只剩:

复制代码
summarizer

这说明:

同一个 State 内,不同字段可以采用完全不同的更新策略。

3. 场景三:自定义 Reducer

merge_dicts

实现:

复制代码
metadata

自动合并。

dedup_list

实现:

复制代码
tags

自动去重。

例如:

复制代码
langgraph

只会保留一份。

七、常见坑与排查

(一)坑 1:忘记添加 Reducer

最终只剩最后一条消息。

错误写法

python 复制代码
class State(TypedDict):
    messages: list[str]

正确写法

python 复制代码
class State(TypedDict):
    messages: Annotated[
        list[str],
        operator.add
    ]

(二)坑 2:Reducer 字段未初始化

直接报错信息

python 复制代码
app.invoke({})

直接报错:

复制代码
KeyError
TypeError

Reducer 需要:

复制代码
old + new

但:

复制代码
None + [...]

这里基本就是没有初始化导致显然不成立。

正确做法

python 复制代码
app.invoke({
    "messages": [],
    "steps": [],
    "current_node": ""
})

(三)坑 3:Reducer 有副作用

错误示例

python 复制代码
def bad(a, b):
    a.extend(b)
    return a

直接修改了旧对象。

正确示例

python 复制代码
def good(a, b):
    return a + b

返回全新对象。

💡 踩坑笔记

我在一开始学习 LangGraph 过程中真实遇到的问题例如:

"我曾经盯着 messages 为什么只剩一条消息排查了五分钟,最后发现只是少写了一个 Annotated。"

这种真实经历往往比知识点本身更有价值,大家快动手操作一下吧。

八、工程化思考与生产实践

(一)工程化思考简要

1. 历史记录无限增长

Reducer 只负责追加,不会自动清理。

因此:

复制代码
messages

会越来越长。最终导致:

  • Token 爆炸
  • 内存上涨

需要定期裁剪。

2. 初始化统一管理

建议不要在每次:

复制代码
invoke()

时手动传空列表。

统一使用工厂函数:

复制代码
def make_initial_state():
    ...

3. Reducer 必须保持纯函数

不要:

  • 写日志
  • 写文件
  • 修改入参

把它当成数学函数即可。

4. 调试难度提升

State 字段越来越多后,很难定位是谁修改了什么。建议结合:

复制代码
stream()

观察每个节点产生的增量更新。

(二)生产级实践建议

1. 统一初始化

复制代码
def make_initial_state():

    return {
        "messages": [],
        "steps": [],
        "current_node": ""
    }

2. 给历史字段加上限

复制代码
def add_capped(
    old,
    new,
    cap=100
):

    return (
        old + new
    )[-cap:]

只保留最近 100 条。

3. 对话场景优先使用 add_messages

复制代码
from langgraph.graph.message import add_messages

class ChatState(TypedDict):

    messages: Annotated[
        list,
        add_messages
    ]

相比:

复制代码
operator.add

它支持:

  • 追加
  • 去重
  • 更新
  • 删除

是生产环境中的首选方案。

九、总结

State 管理是 LangGraph 中最容易被忽视、却又最重要的机制之一。

Node 和 Edge 决定:

Graph 怎么走。

而 Reducer 决定:

数据怎么流。

记住一句最重要的话:

每定义一个 State 字段,都先问自己:它是"当前值",还是"历史记录"?

  • 当前值 → 覆盖
  • 历史记录 → Reducer

掌握这一点后,你就真正拥有了构建复杂 Agent 状态系统的能力。

下一篇预告

掌握了状态管理之后,Graph 已经能够稳定传递数据。

但现实中的 Agent 不会永远沿着一条直线执行:

  • 用户问题不同
  • 工具调用结果不同
  • 模型决策不同

Graph 必须学会"分叉"。

下一篇:

《让 Graph 学会"分叉":Conditional Edge 与路由函数实战》

我们将正式进入 LangGraph 的分支控制世界,学习如何根据状态动态决定下一步执行路径。

而在后续第 4 篇中,我们还会专门深入讲解:

MessagesState 与 add_messages ------ LangGraph 对话场景中的智能 Reducer。

系列导航LangGraph从零构建生产级 AI Agent 平台的递进式学习项目-CSDN博客

相关推荐
程序喵大人1 小时前
C++ 程序员转型 AI Infra 学习路线
c++·人工智能·学习·ai infra
陈天伟教授1 小时前
图解人工智能(45)人工智能应用-语音识别
人工智能·语音识别
白狐_7981 小时前
AI 数据分析 Skill 实战:用模拟游客数据生成文旅运营报告
大数据·服务器·人工智能
数学建模导师1 小时前
【AI生成内容的质量评估】2026中青杯B题26页成品论文重磅更新
人工智能·深度学习·机器学习
comcoo1 小时前
Windows 部署龙虾 AI OpenClaw,快速构建本地私有化 AI 智能体
人工智能·github·开源软件·open claw·open claw部署
段一凡-华北理工大学1 小时前
工业领域的Hadoop架构学习~系列文章13:数据湖架构 - 工业大数据的统一存储底座
大数据·人工智能·hadoop·分布式·架构·高炉炼铁·高炉智能化
俊哥V1 小时前
每日 AI 研究简报 · 2026-06-04
人工智能·ai
回眸&啤酒鸭1 小时前
【回眸】CSDN新增功能测评——AI数字营销之批量生产
人工智能