同一个 ReAct,两种手写方式:LangChain 文本协议 vs LangGraph 状态图

两份代码解决同一个问题:让模型「思考 → 调工具 → 看结果 → 再思考」。

一份用 prompt + 正则 + while 手写(test.py),一份用 StateGraph + ToolNode 手写(langgraph_demo.py)。

本文拆解两种实现,并对比它们在决策、执行、状态、终止和扩展性上的差异。


一、先看两张图

LangChain 版:文本协议驱动
#mermaid-svg-D9N3HyfvvGnioIZa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-D9N3HyfvvGnioIZa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-D9N3HyfvvGnioIZa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-D9N3HyfvvGnioIZa .error-icon{fill:#552222;}#mermaid-svg-D9N3HyfvvGnioIZa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D9N3HyfvvGnioIZa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-D9N3HyfvvGnioIZa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D9N3HyfvvGnioIZa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D9N3HyfvvGnioIZa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-D9N3HyfvvGnioIZa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D9N3HyfvvGnioIZa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D9N3HyfvvGnioIZa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D9N3HyfvvGnioIZa .marker.cross{stroke:#333333;}#mermaid-svg-D9N3HyfvvGnioIZa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D9N3HyfvvGnioIZa p{margin:0;}#mermaid-svg-D9N3HyfvvGnioIZa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-D9N3HyfvvGnioIZa .cluster-label text{fill:#333;}#mermaid-svg-D9N3HyfvvGnioIZa .cluster-label span{color:#333;}#mermaid-svg-D9N3HyfvvGnioIZa .cluster-label span p{background-color:transparent;}#mermaid-svg-D9N3HyfvvGnioIZa .label text,#mermaid-svg-D9N3HyfvvGnioIZa span{fill:#333;color:#333;}#mermaid-svg-D9N3HyfvvGnioIZa .node rect,#mermaid-svg-D9N3HyfvvGnioIZa .node circle,#mermaid-svg-D9N3HyfvvGnioIZa .node ellipse,#mermaid-svg-D9N3HyfvvGnioIZa .node polygon,#mermaid-svg-D9N3HyfvvGnioIZa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D9N3HyfvvGnioIZa .rough-node .label text,#mermaid-svg-D9N3HyfvvGnioIZa .node .label text,#mermaid-svg-D9N3HyfvvGnioIZa .image-shape .label,#mermaid-svg-D9N3HyfvvGnioIZa .icon-shape .label{text-anchor:middle;}#mermaid-svg-D9N3HyfvvGnioIZa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-D9N3HyfvvGnioIZa .rough-node .label,#mermaid-svg-D9N3HyfvvGnioIZa .node .label,#mermaid-svg-D9N3HyfvvGnioIZa .image-shape .label,#mermaid-svg-D9N3HyfvvGnioIZa .icon-shape .label{text-align:center;}#mermaid-svg-D9N3HyfvvGnioIZa .node.clickable{cursor:pointer;}#mermaid-svg-D9N3HyfvvGnioIZa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-D9N3HyfvvGnioIZa .arrowheadPath{fill:#333333;}#mermaid-svg-D9N3HyfvvGnioIZa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-D9N3HyfvvGnioIZa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-D9N3HyfvvGnioIZa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D9N3HyfvvGnioIZa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-D9N3HyfvvGnioIZa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D9N3HyfvvGnioIZa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-D9N3HyfvvGnioIZa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-D9N3HyfvvGnioIZa .cluster text{fill:#333;}#mermaid-svg-D9N3HyfvvGnioIZa .cluster span{color:#333;}#mermaid-svg-D9N3HyfvvGnioIZa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-D9N3HyfvvGnioIZa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-D9N3HyfvvGnioIZa rect.text{fill:none;stroke-width:0;}#mermaid-svg-D9N3HyfvvGnioIZa .icon-shape,#mermaid-svg-D9N3HyfvvGnioIZa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D9N3HyfvvGnioIZa .icon-shape p,#mermaid-svg-D9N3HyfvvGnioIZa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-D9N3HyfvvGnioIZa .icon-shape .label rect,#mermaid-svg-D9N3HyfvvGnioIZa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D9N3HyfvvGnioIZa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-D9N3HyfvvGnioIZa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-D9N3HyfvvGnioIZa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有
无
用户问题
LLM 生成 Thought / Action
正则匹配 Action?
tool_mapname(input)
拼成 'Observation: ...'
返回消息列表

LangGraph 版:状态图驱动
#mermaid-svg-QKEUMUnzVaxBgoHn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QKEUMUnzVaxBgoHn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QKEUMUnzVaxBgoHn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QKEUMUnzVaxBgoHn .error-icon{fill:#552222;}#mermaid-svg-QKEUMUnzVaxBgoHn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QKEUMUnzVaxBgoHn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QKEUMUnzVaxBgoHn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QKEUMUnzVaxBgoHn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QKEUMUnzVaxBgoHn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QKEUMUnzVaxBgoHn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QKEUMUnzVaxBgoHn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QKEUMUnzVaxBgoHn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QKEUMUnzVaxBgoHn .marker.cross{stroke:#333333;}#mermaid-svg-QKEUMUnzVaxBgoHn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QKEUMUnzVaxBgoHn p{margin:0;}#mermaid-svg-QKEUMUnzVaxBgoHn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QKEUMUnzVaxBgoHn .cluster-label text{fill:#333;}#mermaid-svg-QKEUMUnzVaxBgoHn .cluster-label span{color:#333;}#mermaid-svg-QKEUMUnzVaxBgoHn .cluster-label span p{background-color:transparent;}#mermaid-svg-QKEUMUnzVaxBgoHn .label text,#mermaid-svg-QKEUMUnzVaxBgoHn span{fill:#333;color:#333;}#mermaid-svg-QKEUMUnzVaxBgoHn .node rect,#mermaid-svg-QKEUMUnzVaxBgoHn .node circle,#mermaid-svg-QKEUMUnzVaxBgoHn .node ellipse,#mermaid-svg-QKEUMUnzVaxBgoHn .node polygon,#mermaid-svg-QKEUMUnzVaxBgoHn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QKEUMUnzVaxBgoHn .rough-node .label text,#mermaid-svg-QKEUMUnzVaxBgoHn .node .label text,#mermaid-svg-QKEUMUnzVaxBgoHn .image-shape .label,#mermaid-svg-QKEUMUnzVaxBgoHn .icon-shape .label{text-anchor:middle;}#mermaid-svg-QKEUMUnzVaxBgoHn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QKEUMUnzVaxBgoHn .rough-node .label,#mermaid-svg-QKEUMUnzVaxBgoHn .node .label,#mermaid-svg-QKEUMUnzVaxBgoHn .image-shape .label,#mermaid-svg-QKEUMUnzVaxBgoHn .icon-shape .label{text-align:center;}#mermaid-svg-QKEUMUnzVaxBgoHn .node.clickable{cursor:pointer;}#mermaid-svg-QKEUMUnzVaxBgoHn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QKEUMUnzVaxBgoHn .arrowheadPath{fill:#333333;}#mermaid-svg-QKEUMUnzVaxBgoHn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QKEUMUnzVaxBgoHn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QKEUMUnzVaxBgoHn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QKEUMUnzVaxBgoHn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QKEUMUnzVaxBgoHn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QKEUMUnzVaxBgoHn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QKEUMUnzVaxBgoHn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QKEUMUnzVaxBgoHn .cluster text{fill:#333;}#mermaid-svg-QKEUMUnzVaxBgoHn .cluster span{color:#333;}#mermaid-svg-QKEUMUnzVaxBgoHn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QKEUMUnzVaxBgoHn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QKEUMUnzVaxBgoHn rect.text{fill:none;stroke-width:0;}#mermaid-svg-QKEUMUnzVaxBgoHn .icon-shape,#mermaid-svg-QKEUMUnzVaxBgoHn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QKEUMUnzVaxBgoHn .icon-shape p,#mermaid-svg-QKEUMUnzVaxBgoHn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QKEUMUnzVaxBgoHn .icon-shape .label rect,#mermaid-svg-QKEUMUnzVaxBgoHn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QKEUMUnzVaxBgoHn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QKEUMUnzVaxBgoHn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QKEUMUnzVaxBgoHn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 最后一条消息有 tool_calls

且 steps 未超限
无 tool_calls 或超步
START
thought_node

bind_tools + invoke
should_continue
action_node

ToolNode 执行
final_answer_node
END

同样的循环,一个藏在 while 和字符串里,一个显式画在图上。


二、LangChain 版拆解:把协议写进提示词

2.1 决策靠提示词

系统提示词定义了整套「语言协议」:

text 复制代码
Thought: ...
Action: fetch_real_time_info: mass of earth
PAUSE

模型必须按这个格式输出,代码才知道下一步做什么。提示词里还塞了两段 few-shot 示例,教模型「什么时候查、什么时候算、什么时候给 Answer」。

2.2 解析靠正则

python 复制代码
action_re = re.compile(r'^Action: (\w+): (.*)$')

actions = [action_re.match(a) for a in result.split('\n') if action_re.match(a)]
if actions:
    action, action_input = actions[0].groups()
    observation = tool_map[action](action_input)
    next_message = f"Observation: {observation}"

关键点:

  • 一行只能有一个 Action,且格式必须严格是 Action: 工具名: 参数;
  • 只取 actions[0],天然不支持一轮多工具;
  • 工具参数是字符串 ,calculate 里直接 eval,没有类型校验。

2.3 循环靠 while

python 复制代码
while i <= max_time:
    i += 1
    result = chatBot(next_message)
    actions = [...]
    if actions:
        ...
        next_message = f"Observation: {observation}"
    else:
        return chatBot.message

终止条件只有一个:模型不再输出 Action。max_time 用尽时函数没有返回值,会隐式返回 None------这是第一版留下的坑。

2.4 状态靠一个列表

python 复制代码
class ChatBot:
    def __init__(self, promot):
        self.message = [{"role": "system", "content": promot}]

    def __call__(self, message):
        self.message.append({"role": "user", "content": message})
        result = self.excutor()
        self.message.append({"role": "assistant", "content": result})
        return result

对话历史就是 self.message,没有类型、没有 reducer、没有持久化。Observation 是作为下一条 user 消息塞回去的,模型看到的其实是「用户说了 Observation」。


三、LangGraph 版拆解:把循环画成图

3.1 状态显式化

python 复制代码
class AgentState(TypedDict):
    messages: Annotated[List, add_messages]
    answer: str
    is_last_step: int
    steps: int

四个字段各自明确:

  • messages:完整消息历史,add_messages 自动追加;
  • answer:最终答案;
  • steps / is_last_step:轮次计数与上限。

TypedDict 不能给默认值,所以调用时必须显式传:

python 复制代码
graph.invoke({
    "messages": [HumanMessage(content=query)],
    "is_last_step": 5,
    "steps": 0,
})

3.2 三个节点

thought_node:模型决策

python 复制代码
def thought_node(state):
    model_with_tool = model.bind_tools(tools)
    response = model_with_tool.invoke(
        [SystemMessage(content=prompt)] + state["messages"]
    )
    return {"messages": [response], "steps": state["steps"] + 1}

和 LangChain 版最大的区别:这里不要求模型输出特定格式的文本 ,而是把工具 schema 交给模型,让模型直接产出 AIMessage.tool_calls。

action_node:执行工具

python 复制代码
def action_node(state):
    tool_node = ToolNode(tools)
    response = tool_node.invoke({"messages": [state["messages"][-1]]})
    return {"messages": response["messages"]}

ToolNode 读取最后一条 AIMessage 的 tool_calls,逐个执行,返回带 tool_call_id 的 ToolMessage。

final_answer_node:收尾

python 复制代码
def final_answer_node(state):
    if state["steps"] > state["is_last_step"]:
        return {"answer": "推理次数超过最大次数,回答失败"}
    answer = state["messages"][-1].content
    return {"answer": answer}

3.3 两条边构成循环

python 复制代码
def should_continue(state):
    if state["steps"] > state["is_last_step"]:
        return False
    if state["messages"][-1].tool_calls:
        return True
    return False

state_graph.add_conditional_edges(
    "thought_node",
    should_continue,
    {True: "action_node", False: "final_answer_node"},
)
state_graph.add_edge("action_node", "thought_node")
state_graph.add_edge("final_answer_node", END)

这里有三个与 LangChain 版本质不同的点:

  1. 判断依据是结构化的 tool_calls,不是文本;
  2. 循环是图的一部分 ,action → thought 是显式回边;
  3. 终止有两个条件:没有工具调用,或轮次超限,且超限时仍会走到一个明确的结束节点。

四、同一个问题,两条执行轨迹

查询:

text 复制代码
帮我查一下北京今天天气怎么样,并保存一下

LangGraph 版的一条典型轨迹:

轮次 节点 模型/工具动作 状态变化
1 thought_node 调 get_weather(city="北京") steps=1,新增 AIMessage(tool_calls)
1 action_node 执行天气 API 新增 ToolMessage(天气 JSON)
2 thought_node 调 save_weather(weather={...}) steps=2,新增 AIMessage(tool_calls)
2 action_node 执行保存 新增 ToolMessage("已保存")
3 thought_node 不再调工具,直接回答 steps=3,新增 AIMessage(content)
3 final_answer_node 提取最后一条消息正文 answer=...

注意第 2 轮的 save_weather 参数来自第 1 轮的工具结果------这种「后一个工具依赖前一个工具输出」的场景,必须靠循环才能完成,一轮工具调用做不到。

LangChain 版的轨迹形态相同,但每一轮都是一段文本:

text 复制代码
result: Thought: 我需要查北京天气
Action: fetch_real_time_info: 北京天气
...
observation: ...
result: Thought: 天气结果已经拿到,需要保存
Action: ...

一个是消息对象在状态里流动,一个是字符串在提示词里滚动。


五、逐项对比

维度 LangChain 手写版 LangGraph 手写版
决策表达 自然语言协议(Thought/Action) 原生 tool_calls
动作解析 正则 ^Action: (\w+): (.*)$ 无需解析
工具执行 tool_map[name](input) 手动分派 ToolNode 统一执行
参数类型 字符串,eval / 直接传参 JSON Schema + Pydantic 校验
每轮动作数 只取 actions[0],一次一个 一条消息可含多个 tool_calls,一起执行
结果回填 "Observation: ..." 拼进用户消息 ToolMessage + tool_call_id
循环控制 while i <= max_time 条件边 + 回边
终止条件 模型不再输出 Action 无 tool_calls 或 steps 超限
超限行为 隐式返回 None 显式返回「回答失败」
状态 一个消息列表 TypedDict + reducer
可测试性 依赖模型输出格式,难单测 should_continue 是纯函数,可直接断言
可观测性 print + 文本日志 节点级 trace、stream 增量
扩展新分支 改提示词、加 tool_map、祈祷模型照做 加节点与边,编译期校验
提示词负担 需要长 few-shot 示例 一行流程说明即可

一句话总结:

LangChain 版把「协议」写在提示词里,LangGraph 版把「协议」写进类型和边里。


六、两种实现各自的坑

6.1 LangChain 版

  1. 正则太脆 :模型多一个空格、少一个冒号、写成 Action: Beijing.,都会解析失败;
  2. 提示词在教坏模型 :示例里有 Action: Beijing. 这种非法动作,模型会照抄;
  3. 只取第一个 Action:天然不支持并行工具;
  4. max_time 用尽返回 None :调用方拿到 None 而不是错误;
  5. eval 执行模型生成的字符串:学习可以,生产危险;
  6. API Key 硬编码:文件里能直接看到。

6.2 LangGraph 版

  1. 轮次边界会浪费一次调用 :steps 在 thought_node 里自增,第 6 次思考后才判断 6 > 5,这一次模型调用白跑;
  2. final_answer_node 依赖 messages[-1].content:如果模型返回内容为空(只给工具调用或拒答),答案就是空字符串;
  3. 工具返回值缺失 :get_weather 捕获 UapiError 后只 print,没有 return,出错时工具结果是空;
  4. save_weather 参数是 dict:模型要把上一轮 JSON 原样搬过来,字段结构不稳定时容易传错;
  5. 状态无默认值 :is_last_step、steps 必须每次手动传,接前端时容易漏;
  6. 没有 checkpointer:无法多轮会话、无法断点恢复。

这些坑的共同点:图的骨架是对的,边界条件还没打磨。


七、该用哪一种

场景 建议
只想理解 ReAct 的原理 先写 LangChain 文本版,最直观
要接真实工具、多工具、多分支 LangGraph 版,类型和流程都更可控
需要持久化、人工确认、断点恢复 LangGraph + checkpointer
快速原型、一次性脚本 create_agent 或文本版即可
需要向上解释「为什么这么跳」 LangGraph,图能画出来

更准确地说,两种「手写」练的是不同的能力:

  • LangChain 版练的是协议设计:如何让模型稳定输出可解析的动作;
  • LangGraph 版练的是流程设计:如何把判断、执行、终止拆成可组合的节点与边。

八、小结

回头看,两份代码的差别不在「用哪个框架」,而在决策信息的载体:

  • 文本版:模型说的话就是控制流,代码只能猜;
  • 图版本:模型只负责产出结构化决策,控制流由边决定。

当工具只有一个、流程只有一条时,文本版更快;当工具变多、分支变多、需要重试和持久化时,图版本的优势会迅速放大。

而 LangGraph 版离「生产可用」其实只差几步:修正 steps 边界、给 final_answer_node 加兜底、让工具统一返回结构化结果、再挂一个 checkpointer。

循环可以手写,但边界必须显式。

相关推荐
掘黄金的打工人4 小时前
LangChain PromptTemplate 提示词模板
langchain·ai编程
小七-七牛开发者5 小时前
Tool-Use Agent 如何扩训练数据:拆解工具、任务与环境状态的 Scaling Law
ai·agent·工具调用·scalinglaw·synthapi
wflynn8 小时前
Mistral Large 4发布,万亿参数模型的编码与安全实测数据
ai·技术
日日大王学编程8 小时前
AI 时代后端方向新人的未来规划与看法
人工智能·后端·python·ai·golang
土星云SaturnCloud9 小时前
基于边缘计算与AI视觉的智慧街面治理方案:架构设计与场景落地
服务器·人工智能·ai·边缘计算
Elastic 中国社区官方博客9 小时前
基于 Elasticsearch 与 Jina AI 的全模态智能搜索实践:技术全解析
大数据·人工智能·elasticsearch·搜索引擎·ai·信息可视化·jina
日日大王学编程9 小时前
deerflow-memory-design-blog
python·langchain·开源
Solange__9 小时前
图片转3D导出GLB、FBX还是OBJ?按5类任务比较材质、动画与可编辑性
人工智能·3d·ai·自然语言处理·编辑器·blender·材质
GJGCY10 小时前
金融AI Agent安全如何评估?数据出站、身份映射、参数绑定与审计追踪机制
人工智能·经验分享·安全·ai·金融·自动化·智能体