news 2026/10/9 4:37:23

LangGraph 7. Skills 1. 技能 Skills(附完整免费源代码):把 SKILL.md 改到 TaoToken 的 Agent Skills 落地大纲

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangGraph 7. Skills 1. 技能 Skills(附完整免费源代码):把 SKILL.md 改到 TaoToken 的 Agent Skills 落地大纲

1. 从 SKILL.md 到可运行技能:LangGraph Agent Skills 落地要解决的真问题

如果你正在用 LangGraph 搭 Agent,大概率遇到过这种局面:研究、写作、评审、PDF 处理这些能力全塞在节点函数里,每加一种能力就要改图、改状态定义、改边。图越画越肿,能力却没法单独测试,更别说复用到另一个项目。Agent Skills 想解决的就是这件事——把能力从主流程里拆出来,做成可发现、可加载、可替换的单元。

Agent Skills 的业界标准形态很朴素:一个技能就是一个文件夹,里面至少有一个SKILL.md,由 YAML frontmatter 加 Markdown 正文组成。frontmatter 里必须有name和description,正文写清楚这个技能怎么用。Agent 的运行流程被拆成四步:发现(只读 frontmatter,约 100 tokens/技能)、选择(按任务匹配)、加载(只对选中的技能读完整正文)、使用(把技能说明作为上下文交给 LLM 执行,或直接跑技能目录下的脚本)。这套流程叫 Progressive disclosure,按需加载,不浪费上下文。

这篇面向的是想用统一 Key/API 通道跑通技能编排的开发者。我会给出skills_library/目录结构、skill_loader.py的注册与调用配置,以及把 endpoint 改到 TaoToken 后的连通性验证动作,附完整免费源代码。适合谁:已经写过 LangGraph 节点、想让能力可插拔的人;或者刚接触 Agent Skills、想先跑通一个最小闭环的人。读完你能拿到一个能直接python main.py跑起来的示例,技能库放进去就能被自动发现。

2. TaoToken 前置:统一 Key/API 通道怎么接进技能执行链

技能本身不绑定模型,但「使用」这一步要调 LLM。示例里use_skill_with_llm()用的是langchain_openai的ChatOpenAI,它接受api_key、base_url、model三个参数。把这三个参数指向 TaoToken,整条技能执行链就走同一个通道,不用为每个技能单独配 Key。

TaoToken 在这里的角色是统一入口:一个 Key、一个 Base URL,模型 ID 按需切换。对 Agent Skills 这种「技能多、调用散」的场景很合适——技能库可以随便加,通道不用动。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api (这个不加 UTM)。

你需要准备的东西不多:一个可用的 API Key,一个想用的模型 ID,以及把base_url设成https://taotoken.net/api。Key 的获取在控制台完成,路径是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Keys 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。如果你还没想好模型,可以先去模型对话页试一下 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,确认通道通了再写进.env。

这里有个容易踩的点:ChatOpenAI的base_url要写到/api这一层,不要自己拼/v1或别的后缀,否则会出现 404 或路径重复。另一个点是api_key不要硬编码在skill_loader.py里,统一走config_parser.py从.env读,这样技能库换项目时不用改代码。如果你后续要做长期编码或 Agent 编排,可以了解下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

3. 可复制配置:SKILL.md 目录结构、skill_loader 与 .env

先把目录结构定下来。技能库根目录是demo_codes/skills_library/,每个子文件夹是一个技能,里面必须有SKILL.md。一个符合规范的技能长这样:

demo_codes/ ├── skills_library/ │ ├── README.md │ ├── summarize/ │ │ └── SKILL.md │ ├── pdf/ │ │ └── SKILL.md │ └── doc-coauthoring/ │ └── SKILL.md ├── skill_loader.py ├── config_parser.py ├── main.py ├── requirements.txt └── .env

SKILL.md的 frontmatter 用 YAML,正文用 Markdown。一个最小可用的summarize/SKILL.md:

--- name: summarize description: 把长文本压缩成要点并保留来源回链,适合信息汇总场景。 --- # Summarize 当用户要求总结、摘要、压缩内容时使用本技能。 ## 步骤 1. 通读输入,识别主题与关键结论。 2. 输出 3-5 条要点,每条不超过 40 字。 3. 若原文有链接或出处,保留回链。

skill_loader.py里四个函数对应四步流程。发现阶段只解析 frontmatter:

import re from pathlib import Path from typing import Dict, List, Optional, Tuple SKILL_FILENAME = "SKILL.md" DEFAULT_SKILLS_LIBRARY = Path(__file__).parent / "skills_library" FRONTMATTER_PATTERN = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL) def _parse_frontmatter(raw: str) -> Dict[str, str]: m = FRONTMATTER_PATTERN.match(raw) if not m: return {} meta: Dict[str, str] = {} for line in m.group(1).splitlines(): if ":" in line: k, v = line.split(":", 1) meta[k.strip()] = v.strip().strip('"').strip("'") return meta def discover_skills(skills_root: Optional[Path] = None) -> List[Dict[str, str]]: root = Path(skills_root) if skills_root else DEFAULT_SKILLS_LIBRARY if not root.is_dir(): return [] result: List[Dict[str, str]] = [] for path in sorted(root.iterdir()): if not path.is_dir(): continue skill_md = path / SKILL_FILENAME if not skill_md.is_file(): continue try: raw = skill_md.read_text(encoding="utf-8", errors="replace") except OSError: continue meta = _parse_frontmatter(raw) if meta.get("name"): result.append(meta) return result

选择阶段先用关键词匹配,生产环境可以换成 LLM 判断:

def select_skill_for_task(task: str, discovered: List[Dict[str, str]]) -> Optional[str]: task_lower = (task or "").strip().lower() if not task_lower or not discovered: return discovered[0]["name"] if discovered else None keywords = [ (["总结", "摘要", "summarize", "压缩", "汇总"], "summarize"), (["pdf", "文档"], "pdf"), (["doc", "word", "docx"], "docx"), (["协作", "coauthor", "文档协作"], "doc-coauthoring"), ] for kws, name in keywords: if any(k in task_lower for k in kws): for d in discovered: if d.get("name") == name: return name return discovered[0].get("name") if discovered else None

加载阶段只对选中的技能读完整正文,去掉 frontmatter 后交给 LLM:

def load_skill(skill_name: str, skills_root: Optional[Path] = None) -> Tuple[str, str]: root = Path(skills_root) if skills_root else DEFAULT_SKILLS_LIBRARY skill_md = root / skill_name / SKILL_FILENAME if not skill_md.is_file(): return "", "" try: full = skill_md.read_text(encoding="utf-8", errors="replace") except OSError: return "", "" body = full m = FRONTMATTER_PATTERN.match(full) if m: body = full[m.end():].strip() return full, body

使用阶段把技能说明和用户输入一起交给 LLM,这里就是接 TaoToken 的地方:

def use_skill_with_llm( skill_name: str, skill_instructions: str, user_input: str, api_key: Optional[str] = None, base_url: Optional[str] = None, model: Optional[str] = None, ) -> str: try: from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_openai import ChatOpenAI except ImportError: return "[需要安装 langchain-openai 才能使用 LLM 执行技能]" if not api_key or not str(api_key).strip(): try: from config_parser import skills_config api_key = skills_config.api_key or "" base_url = base_url if base_url is not None else (skills_config.base_url or "") model = model or skills_config.model except Exception: return "[请配置 .env 中的 OPENAI_API_KEY]" llm = ChatOpenAI( model=(model or "gpt-4o-mini"), api_key=api_key, base_url=base_url if base_url else None, temperature=0.3, ) prompt = ChatPromptTemplate.from_messages([ ("system", "你正在使用名为「{skill_name}」的 Agent Skill。请严格按照以下技能说明执行用户请求。\n\n技能说明:\n{skill_instructions}"), ("user", "{user_input}"), ]) chain = prompt | llm | StrOutputParser() return (chain.invoke({ "skill_name": skill_name, "skill_instructions": skill_instructions or "(无额外说明)", "user_input": user_input, }) or "").strip()

config_parser.py从.env读配置,.env内容如下:

OPENAI_API_KEY=你的_TaoToken_Key BASE_URL=https://taotoken.net/api MODEL=你的模型ID

config_parser.py:

import os from dataclasses import dataclass from dotenv import load_dotenv load_dotenv() @dataclass class SkillsConfig: api_key: str = os.getenv("OPENAI_API_KEY", "") base_url: str = os.getenv("BASE_URL", "https://taotoken.net/api") model: str = os.getenv("MODEL", "gpt-4o-mini") skills_config = SkillsConfig()

requirements.txt写这几行:

langchain-openai langchain-core python-dotenv

main.py把四步串起来:

from skill_loader import discover_skills, select_skill_for_task, load_skill, use_skill_with_llm def run(task: str) -> None: discovered = discover_skills() if not discovered: raise SystemExit("未发现任何技能,请按 skills_library/README.md 放置技能。") print("已发现技能(仅元数据):") for d in discovered: desc = (d.get("description") or "")[:60] print(f" - {d.get('name', '')}: {desc}") skill_name = select_skill_for_task(task, discovered) or discovered[0]["name"] print("选中技能:", skill_name) _, body = load_skill(skill_name) if not body: raise SystemExit(f"无法加载技能 {skill_name} 的 SKILL.md") print(f"已加载技能正文(约 {len(body)} 字符)") result = use_skill_with_llm(skill_name, body, task) print("结果:", result) if __name__ == "__main__": import sys run(sys.argv[1] if len(sys.argv) > 1 else "请总结并保留要点:LangGraph 是用于构建多步推理与智能体工作流的库,支持状态图与条件边。")

4. 验证请求:跑通发现→选择→加载→使用并确认通道连通

配置齐了,先装依赖再跑。进入demo_codes目录:

pip install -r requirements.txt python main.py

预期输出分四段。第一段是发现结果,只打印元数据,不读正文:

已发现技能(仅元数据): - doc-coauthoring: Guide users through a structured workflow for co-authoring d... - pdf: Use this skill whenever the user wants to do anything with P... - summarize: 把长文本压缩成要点并保留来源回链,适合信息汇总场景。

第二段是选择结果,任务里带「总结」关键词,应该命中summarize:

选中技能: summarize

第三段是加载结果,只对选中的技能读完整正文:

已加载技能正文(约 320 字符)

第四段是使用结果,这一步真正打到 TaoToken 的/api端点。如果通道通了,会返回模型生成的要点文本;如果 Key 或 Base URL 有问题,这里会报错,下一节专门排。

想验证「选择」逻辑是否按任务走,可以换一个任务再跑:

python main.py "用三句话概括 PDF 文档处理常见需求。"

这次任务里带「PDF」,应该命中pdf技能,加载的正文长度也会变。这一步能确认发现和选择是解耦的——发现阶段不读正文,选择阶段只比对元数据,加载阶段才读正文。你可以故意把某个技能的description改掉,观察选择结果是否变化,以此确认匹配逻辑生效。

如果要在 LangGraph 图里用,把「使用技能」做成一个节点即可,节点内部依次调discover_skills、select_skill_for_task、load_skill、use_skill_with_llm,状态里存task和result。这样技能库增删不影响图结构,只影响节点内部的发现结果。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

跑不通时先看报错落在哪一步。发现和加载阶段不碰网络,报错基本是路径问题;使用阶段才碰网络,报错集中在 Key、Base URL、模型 ID。

401 Unauthorized:api_key没读到或值不对。先确认.env里OPENAI_API_KEY有值且没有多余空格,再确认config_parser.py的load_dotenv()在读取前执行。如果.env放在demo_codes目录,而你在别的目录跑python main.py,load_dotenv()可能找不到文件,用绝对路径或先cd到demo_codes。还有一种情况是 Key 复制时带了换行,strip 一下。

local proxy failed / connection error:BASE_URL写错或网络不通。确认值是https://taotoken.net/api,不要写成https://taotoken.net/api/v1或漏掉https。如果本机有环境变量HTTP_PROXY、HTTPS_PROXY指向了不可用的地址,langchain_openai会走这个代理导致连接失败,临时清掉再试。注意这里说的是本机环境变量,不是让你去配什么网络工具。

reading choices / KeyError 'choices':响应结构不是预期的 OpenAI 格式,通常是base_url指到了错误路径,或者model填了一个该通道不支持的 ID。先确认base_url是https://taotoken.net/api,再确认MODEL是通道里可用的模型 ID。可以先用模型对话页发一条消息,确认这个模型 ID 能出结果,再写回.env。

OAuth / authentication 相关报错:如果你用的是需要 OAuth 的客户端(比如某些 CLI 工具),它的凭据文件和.env是两套东西。本篇示例走的是 API Key,不涉及 OAuth。如果你在别的工具里遇到 OAuth 报错,检查那个工具自己的凭据配置,不要和这里的.env混用。

技能没被发现:discover_skills返回空列表。检查skills_library/下每个技能文件夹里是否有SKILL.md,文件名大小写是否一致,frontmatter 是否有name字段。_parse_frontmatter只认---包裹的 YAML,如果SKILL.md开头没有 frontmatter,这个技能会被跳过。

加载正文为空:load_skill返回空字符串。检查技能名和文件夹名是否一致,select_skill_for_task返回的名字要能在skills_library/下找到同名文件夹。如果 frontmatter 解析后正文被切没了,检查---是否成对出现。

6. 把技能接进你的项目:从示例到可复用技能库

示例跑通后,最有价值的动作是把你项目里重复出现的能力抽成技能。判断标准很简单:如果一段逻辑在多个图或产品里都要用,且它有自己的输入输出说明,就适合做成SKILL.md。抽的时候只写「怎么用」,不写「怎么实现」——实现可以放在技能目录的scripts/下,正文里引用脚本路径即可。

技能库可以独立成仓库,多个项目共用。skill_loader.py里的DEFAULT_SKILLS_LIBRARY改成环境变量或参数传入,就能指向不同技能库。选择逻辑从关键词匹配升级到 LLM 判断时,把候选技能的name和description拼成 prompt 让模型选,返回技能名,其余流程不变。

如果你要把这套接进 Claude Code 或类似工具,配置三件套是 Base URL、Key、Model ID:Base URL 用https://taotoken.net/api,Key 用控制台生成的,Model ID 按需选。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Keys 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。长期做编码或 Agent 编排的话,Coding Plan 在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。

最后留一个实操建议:先别急着加很多技能,放两个进去,把发现、选择、加载、使用四步的输出都打印出来看一遍。确认每一步的输入输出符合预期,再加第三个。技能库的扩展成本很低,但流程没跑通时加技能只会让排查变难。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 4:36:33

题解:洛谷 AT_abc425_b [ABC425B] Find Permutation 2

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

作者头像 李华
网站建设 2026/10/9 4:35:32

架构设计中的Protobuf实践:从序列化原理到跨语言通信的最佳方案

1. 为什么架构设计里要专门留一章给Protobuf1.1 从一个跨语言通信的痛点说起先分享一个我踩过的坑。有一段时间,我在负责一个内部系统的接口改造,上游是Java写的核心服务,下游是Python写的离线分析模块,中间还有几个Node.js的网关…

作者头像 李华
网站建设 2026/10/9 4:33:47

数据转换工具workbuddy-to-dsh使用教程与配置详解

workbuddy-to-dsh使用教程做数据交接的同学应该都有这种体会:别人丢过来一份工时记录表,格式看着挺规整,但要落进自己的数据处理管线里,处处都是坑。要么编码不对,要么字段名对不上,要么同一个员工在同一天…

作者头像 李华
网站建设 2026/10/9 4:33:36

C#模拟经营游戏源码解析:sln工程结构与工具升级采集逻辑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/9 4:33:11

VM虚拟机去虚拟化:让鲁大师识别为物理机的VMX配置指南

简介:面向VMware虚拟机进阶用户的去虚拟化实战教程,核心目标是修改虚拟机底层配置,让鲁大师等硬件检测工具误判为真实物理机,适用于运行特定硬件检测或调试虚拟化兼容性的场景。压缩包内为1个doc文档,大小约76KB&#…

作者头像 李华
网站建设 2026/10/9 4:32:05

从随堂练习到课程设计:操作系统核心算法实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华