把开源 Skills 集成到 LangGraph 项目

13 阅读7分钟

把开源 Skills 集成到 LangGraph 项目中,核心是先将开源 Skill 标准化封装为 LangGraph 可识别的“工具/子图”,再通过状态机编排调用逻辑。下面我会给你一套通用、可落地的完整流程,包含具体代码示例和适配不同类型开源 Skill 的方法。


一、集成前的核心准备

1. 明确开源 Skill 的类型(先分类,再适配)

不同开源 Skill 的实现形式不同,对应集成方式也不同,先做分类:

Skill 类型典型示例集成形式
原子函数型Anthropic 官方技能库中的“文档解析”“代码生成”封装为 LangChain Tool
流程任务型Superpowers 中的“TDD 开发流程”“Git 工作流”封装为 LangGraph 子图(Subgraph)
浏览器自动化型Vercel Agent Browser、Qoder 的 /browser 技能封装为带外部依赖的 Tool
数据处理型Awesome Agent Skills 中的“结构化数据汇总”封装为 Tool + 状态处理函数

2. 环境依赖安装

先安装 LangGraph 及技能集成所需的核心依赖:

# 核心依赖
pip install langgraph langchain langchain-core langchain-community
# 可选:浏览器自动化/工具调用依赖
pip install playwright python-dotenv  # 浏览器操作
playwright install chrome  # 安装浏览器驱动

二、通用集成流程(以 Anthropic 开源 Skill 为例)

步骤 1:下载并解析开源 Skill

以 Anthropic 技能库中的 browser-test-skill 为例,先克隆仓库并提取核心逻辑:

# 克隆开源技能库
git clone https://github.com/anthropics/skills.git
cd skills

打开技能目录下的 SKILL.md(技能元数据)和 main.py(核心逻辑),提取关键功能(比如“浏览器自动化测试”)。

步骤 2:封装开源 Skill 为 LangChain Tool

这是最基础、最通用的集成方式,把开源 Skill 的核心函数包装成 LangGraph 可调用的 Tool:

from langchain.tools import tool
from typing import Optional

# 1. 引入开源 Skill 的核心逻辑(这里模拟 Anthropic 的浏览器测试 Skill)
# 实际使用时替换为你下载的开源 Skill 代码
def run_browser_test(task: str, screenshot: bool = True) -> str:
    """
    开源 Skill 核心逻辑:执行浏览器自动化测试
    :param task: 自然语言测试指令(如“添加2个商品到购物车并验证数量”)
    :param screenshot: 是否生成截图
    :return: 测试结果 + 截图路径(如有)
    """
    from playwright.sync_api import sync_playwright
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        page.goto("https://你的测试网站.com")
        # 这里是开源 Skill 的核心操作逻辑(省略具体步骤)
        result = f"测试完成:{task},截图已保存至 ./test-screenshot.png"
        browser.close()
        return result

# 2. 封装为 LangChain Tool(LangGraph 可直接调用)
@tool
def anthropic_browser_test_skill(
    task: str,
    screenshot: Optional[bool] = True
) -> str:
    """
    封装后的开源 Skill:浏览器自动化测试
    参数说明:
    - task: 自然语言测试指令(必填)
    - screenshot: 是否生成测试截图(默认True)
    """
    try:
        return run_browser_test(task, screenshot)
    except Exception as e:
        return f"技能执行失败:{str(e)}"

步骤 3:定义 LangGraph 状态与智能体节点

状态是 LangGraph 的核心,所有 Skill 的输入/输出都通过状态传递:

from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from typing import List, TypedDict

# 1. 定义智能体状态(必须包含 messages,其他字段按需扩展)
class AgentState(TypedDict):
    messages: List[HumanMessage | AIMessage | ToolMessage]  # 消息流(核心)
    skill_output: Optional[str]  # 存储 Skill 执行结果
    next_node: str  # 控制流程跳转

# 2. 定义智能体决策节点(决定是否调用 Skill)
def agent_decision_node(state: AgentState) -> AgentState:
    """
    智能体决策逻辑:根据用户输入判断是否调用开源 Skill
    """
    # 获取最后一条用户消息
    last_message = state["messages"][-1]
    user_input = last_message.content.lower()

    # 触发条件:包含“浏览器测试”“购物车测试”等关键词时调用 Skill
    if any(keyword in user_input for keyword in ["浏览器测试", "购物车测试", "e2e测试"]):
        # 告诉智能体下一步调用 Tool 节点
        state["next_node"] = "tool_node"
        # 追加调用指令到消息流
        state["messages"].append(
            AIMessage(content="将调用浏览器测试技能执行你的指令")
        )
    else:
        # 不调用 Skill,直接结束
        state["next_node"] = END
        state["messages"].append(
            AIMessage(content="你的指令无需调用技能,任务完成")
        )
    return state

# 3. 注册封装好的开源 Skill 为 ToolNode
# 把所有封装的开源 Skill 放入工具列表
tools = [anthropic_browser_test_skill]
# 创建 LangGraph 工具节点(自动处理 Tool 调用)
tool_node = ToolNode(tools)

# 4. 定义 Tool 执行后的结果处理节点
def tool_result_node(state: AgentState) -> AgentState:
    """
    处理 Skill 执行结果,更新状态
    """
    # 获取 Tool 执行结果
    tool_result = state["messages"][-1].content
    # 保存结果到状态
    state["skill_output"] = tool_result
    # 处理完成后返回给智能体,结束流程
    state["next_node"] = END
    state["messages"].append(
        AIMessage(content=f"技能执行结果:{tool_result}")
    )
    return state

步骤 4:编排 LangGraph 并运行

将决策节点、Tool 节点、结果节点串联成图,完成集成:

# 1. 创建状态机图
graph = StateGraph(AgentState)

# 2. 添加节点
graph.add_node("agent_decision", agent_decision_node)  # 决策节点
graph.add_node("tool_node", tool_node)  # Skill 执行节点
graph.add_node("tool_result", tool_result_node)  # 结果处理节点

# 3. 定义边(流程跳转规则)
# 起始节点 → 决策节点
graph.set_entry_point("agent_decision")
# 决策节点 → Tool 节点(需要调用 Skill 时)
graph.add_conditional_edges(
    "agent_decision",
    lambda x: x["next_node"],  # 根据 next_node 决定跳转
    {
        "tool_node": "tool_node",
        END: END
    }
)
# Tool 节点 → 结果处理节点
graph.add_edge("tool_node", "tool_result")
# 结果处理节点 → 结束
graph.add_edge("tool_result", END)

# 4. 编译图(核心步骤)
app = graph.compile()

# 5. 测试运行(调用集成的开源 Skill)
if __name__ == "__main__":
    # 输入:触发开源 Skill 的用户指令
    initial_state = AgentState(
        messages=[HumanMessage(content="用浏览器测试购物车流程:添加2个商品并验证数量")],
        skill_output=None,
        next_node=""
    )

    # 运行图
    result = app.invoke(initial_state)

    # 输出结果
    print("=== 最终结果 ===")
    print(result["skill_output"])
    print("=== 消息流 ===")
    for msg in result["messages"]:
        print(f"{msg.type}: {msg.content}")

三、进阶:集成流程型开源 Skill 为子图

对于多步骤的开源 Skill(如 Superpowers 的“TDD 开发流程”),需要封装为子图再嵌入主图:

from langgraph.graph import StateGraph

# 1. 定义子图(封装流程型开源 Skill)
def create_tdd_skill_subgraph():
    """
    封装 Superpowers 的 TDD 开发流程 Skill 为子图
    流程:需求分析 → 编写测试用例 → 编写代码 → 运行测试 → 总结
    """
    subgraph = StateGraph(AgentState)

    # 子图节点:对应开源 Skill 的每一步
    def tdd_analysis_node(state: AgentState) -> AgentState:
        # 开源 Skill 逻辑:需求分析
        state["messages"].append(AIMessage(content="TDD 第一步:需求分析完成"))
        state["next_node"] = "tdd_write_test"
        return state

    def tdd_write_test_node(state: AgentState) -> AgentState:
        # 开源 Skill 逻辑:编写测试用例
        state["messages"].append(AIMessage(content="TDD 第二步:测试用例编写完成"))
        state["next_node"] = "tdd_write_code"
        return state

    # 省略其他节点(编写代码、运行测试)...

    # 添加子图节点并编排流程
    subgraph.add_node("tdd_analysis", tdd_analysis_node)
    subgraph.add_node("tdd_write_test", tdd_write_test_node)
    # ... 其他节点
    subgraph.set_entry_point("tdd_analysis")
    subgraph.add_edge("tdd_analysis", "tdd_write_test")
    # ... 其他边
    subgraph.add_edge("tdd_run_test", END)

    return subgraph.compile()

# 2. 在主图中集成子图
if __name__ == "__main__":
    # 创建主图
    main_graph = StateGraph(AgentState)
    # 注册子图(流程型开源 Skill)
    tdd_subgraph = create_tdd_skill_subgraph()
    main_graph.add_node("tdd_skill", tdd_subgraph)

    # 主图决策节点:触发子图
    def main_decision_node(state: AgentState) -> AgentState:
        if "TDD 开发" in state["messages"][-1].content:
            state["next_node"] = "tdd_skill"
        else:
            state["next_node"] = END
        return state

    # 编排主图
    main_graph.add_node("main_decision", main_decision_node)
    main_graph.set_entry_point("main_decision")
    main_graph.add_conditional_edges(
        "main_decision",
        lambda x: x["next_node"],
        {"tdd_skill": "tdd_skill", END: END}
    )
    main_graph.add_edge("tdd_skill", END)

    # 编译并运行
    main_app = main_graph.compile()
    initial_state = AgentState(
        messages=[HumanMessage(content="用 TDD 流程开发一个加法函数")],
        skill_output=None,
        next_node=""
    )
    result = main_app.invoke(initial_state)
    print(result["messages"])

四、关键避坑与优化技巧

  1. 状态传递要规范:所有 Skill 的输入/输出必须通过 AgentState 传递,禁止使用全局变量,否则多轮调用会出错。
  2. 异常处理不可少:在 Tool 封装时加入 try-except,避免单个开源 Skill 崩溃导致整个智能体挂掉。
  3. 技能触发条件要明确:通过关键词、意图识别(如用 LLM 解析用户输入)触发 Skill,避免误调用。
  4. 依赖隔离:不同开源 Skill 的依赖可能冲突,建议用虚拟环境(venv)或 Docker 隔离。
  5. 调试用 LangSmith:开启 LangSmith 跟踪 Skill 调用链路,定位执行中的问题:
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_API_KEY"] = "你的 LangSmith API 密钥"

总结

  1. 开源 Skill 集成到 LangGraph 的核心是分类封装:原子技能→Tool,流程技能→子图。
  2. 所有 Skill 的输入/输出必须通过 LangGraph 的 State 传递,确保流程可追溯。
  3. 关键步骤:下载解析 Skill → 封装为 Tool/子图 → 编排状态机 → 测试运行。