{"slug": "build-skill-augmented-ai-agents-with-skillnet-for-search-evaluation-graph-and", "title": "Build Skill-Augmented AI Agents with SkillNet for Search, Evaluation, Graph Analysis, and Task Planning", "summary": "SkillNet, a framework for discovering, installing, evaluating, and organizing reusable AI skills, has been released as an open-source tool that enables developers to build skill-augmented AI agents. The platform supports keyword and semantic search for finding skills, quality evaluation across multiple dimensions, graph-based visualization of skill relationships, and automated task planning that breaks complex goals into subtasks with relevant skill pipelines. The implementation provides both an SDK and REST API fallback, allowing developers to integrate curated skills from GitHub into agent workflows for search, evaluation, graph analysis, and task planning applications.", "body_md": "In this tutorial, we implement a[ SkillNet](https://github.com/zjunlp/SkillNet) use case as a practical framework for discovering, installing, inspecting, evaluating, and organizing reusable AI skills. We start by setting up a robust SkillNet client with SDK and REST fallback support, then compare keyword search with semantic search to understand how skills can be found for different task requirements. From there, we install curated skills from GitHub, inspect their metadata, apply a quality gate across key evaluation dimensions, and visualize relationships between skills as a graph. Finally, we build a skill-augmented agent planner that breaks a complex goal into subtasks, discovers relevant skills, filters them, and assembles an execution pipeline.\n\n``` python\nimport sys, subprocess\ndef _pip(*pkgs):\n   subprocess.run([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", *pkgs], check=False)\nprint(\"Installing dependencies (skillnet-ai, networkx, matplotlib, requests)...\")\n_pip(\"skillnet-ai\", \"networkx\", \"matplotlib\", \"requests\")\nimport os, re, json, textwrap, pathlib, traceback\nimport requests\nAPI_KEY  = os.environ.get(\"API_KEY\", \"\")\nBASE_URL = os.environ.get(\"BASE_URL\", \"https://api.openai.com/v1\")\nMODEL    = os.environ.get(\"SKILLNET_MODEL\", \"gpt-4o\")\nGITHUB_TOKEN = os.environ.get(\"GITHUB_TOKEN\", \"\")\nGITHUB_MIRROR = os.environ.get(\"GITHUB_MIRROR\", \"\")\nif not API_KEY:\n   try:\n       from google.colab import userdata\n       API_KEY = userdata.get(\"API_KEY\") or \"\"\n   except Exception:\n       pass\nREST_BASE = \"http://api-skillnet.openkg.cn/v1\"\nWORKDIR   = pathlib.Path(\"./skillnet_demo\"); WORKDIR.mkdir(exist_ok=True)\nSKILLS_DIR = WORKDIR / \"my_skills\"; SKILLS_DIR.mkdir(exist_ok=True)\ndef banner(title):\n   line = \"=\" * 78\n   print(f\"\\n{line}\\n  {title}\\n{line}\")\n```\n\nWe install the required dependencies and prepare the basic environment for the SkillNet tutorial. We configure API keys, model settings, GitHub options, and working directories to ensure the rest of the workflow runs smoothly. We also define a reusable banner function to keep the tutorial output organized and readable.\n\n```\nbanner(\"1) Initialize SkillNet client (SDK with REST fallback)\")\nUSE_SDK = False\nclient = None\ntry:\n   from skillnet_ai import SkillNetClient\n   client = SkillNetClient(\n       api_key=API_KEY or None,\n       base_url=BASE_URL,\n       github_token=GITHUB_TOKEN or None,\n   )\n   USE_SDK = True\n   print(\"SDK loaded: skillnet_ai.SkillNetClient\")\nexcept Exception as e:\n   print(f\"SDK unavailable ({e!r}); using REST fallback for search/download.\")\ndef _norm(item):\n   if isinstance(item, dict):\n       g = item.get\n   else:\n       g = lambda k, d=None: getattr(item, k, d)\n   return {\n       \"skill_name\":        g(\"skill_name\") or g(\"name\") or \"?\",\n       \"skill_description\": g(\"skill_description\") or g(\"description\") or \"\",\n       \"author\":            g(\"author\") or \"\",\n       \"stars\":             g(\"stars\") or 0,\n       \"skill_url\":         g(\"skill_url\") or g(\"url\") or \"\",\n       \"category\":          g(\"category\") or \"\",\n   }\ndef search(q, mode=\"keyword\", limit=5, min_stars=0, sort_by=\"stars\", threshold=0.8):\n   if USE_SDK:\n       try:\n           kw = dict(q=q, limit=limit, mode=mode)\n           if mode == \"keyword\":\n               kw.update(min_stars=min_stars, sort_by=sort_by)\n           else:\n               kw.update(threshold=threshold)\n           res = client.search(**kw)\n           return [_norm(x) for x in (res or [])]\n       except Exception as e:\n           print(f\"  [SDK search failed -> REST] {e!r}\")\n   params = {\"q\": q, \"mode\": mode, \"limit\": limit}\n   if mode == \"keyword\":\n       params.update(min_stars=min_stars, sort_by=sort_by)\n   else:\n       params.update(threshold=threshold)\n   try:\n       r = requests.get(f\"{REST_BASE}/search\", params=params, timeout=30)\n       r.raise_for_status()\n       return [_norm(x) for x in r.json().get(\"data\", [])]\n   except Exception as e:\n       print(f\"  [REST search failed] {e!r}\")\n       return []\ndef show_results(results, title=\"\"):\n   if title:\n       print(f\"\\n-- {title} --\")\n   if not results:\n       print(\"   (no results / endpoint unreachable)\")\n       return\n   for i, s in enumerate(results, 1):\n       desc = textwrap.shorten(s[\"skill_description\"], 70, placeholder=\"...\")\n       print(f\"  {i}. {s['skill_name']:<34} ⭐{s['stars']:<5} [{s['category']}]\")\n       if desc:\n           print(f\"     {desc}\")\nbanner(\"2) Search: keyword vs. semantic (vector)\")\nkw_hits  = search(\"pdf\", mode=\"keyword\", limit=5, sort_by=\"stars\")\nshow_results(kw_hits, \"keyword: 'pdf' (sorted by stars)\")\nvec_hits = search(\"analyze financial reports from documents\",\n                 mode=\"vector\", limit=5, threshold=0.80)\nshow_results(vec_hits, \"vector: 'analyze financial reports from documents'\")\n```\n\nWe initialize the SkillNet client and provide a REST fallback, so the tutorial remains usable even if the SDK does not work. We define helper functions to normalize search results and perform both keyword and semantic searches. We then compare a keyword search for PDF-related skills with a vector search for analyzing financial reports from documents.\n\n```\nbanner(\"3) Install skills (download from GitHub into ./skillnet_demo/my_skills)\")\nCURATED = [\n   \"https://github.com/anthropics/skills/tree/main/skills/skill-creator\",\n   \"https://github.com/anthropics/skills/tree/main/skills/algorithmic-art\",\n]\nfor s in (kw_hits + vec_hits):\n   if s[\"skill_url\"] and s[\"skill_url\"] not in CURATED:\n       CURATED.append(s[\"skill_url\"])\nCURATED = CURATED[:4]\ndef download(url, target_dir):\n   if USE_SDK:\n       try:\n           kw = {}\n           if GITHUB_MIRROR:\n               kw[\"mirror\"] = GITHUB_MIRROR\n           return client.download(url=url, target_dir=str(target_dir), **kw)\n       except TypeError:\n           return client.download(url=url, target_dir=str(target_dir))\n       except Exception as e:\n           print(f\"  download failed for {url}: {e!r}\")\n           return None\n   print(\"  (SDK not present — skipping live download for this URL)\")\n   return None\ninstalled = []\nfor url in CURATED:\n   print(f\"  downloading: {url}\")\n   path = download(url, SKILLS_DIR)\n   if path:\n       installed.append(path)\n       print(f\"    -> {path}\")\nprint(f\"\\nInstalled {len(installed)} skill(s).\")\n```\n\nWe create a curated list of useful SkillNet-compatible skills and expand it using the search results collected earlier. We download selected skills from GitHub into a local skills directory when the SDK is available. We keep the installation process small and quick, so the tutorial remains practical for Google Colab.\n\n``` python\nbanner(\"4) Inspect installed skills (SKILL.md frontmatter)\")\ndef parse_skill_md(skill_path):\n   p = pathlib.Path(skill_path)\n   md = None\n   if p.is_dir():\n       for cand in p.rglob(\"SKILL.md\"):\n           md = cand; break\n   elif p.name.upper() == \"SKILL.MD\":\n       md = p\n   if not md or not md.exists():\n       return {\"path\": str(skill_path), \"name\": p.name, \"meta\": {}, \"found\": False}\n   text = md.read_text(encoding=\"utf-8\", errors=\"ignore\")\n   meta = {}\n   m = re.match(r\"^---\\s*\\n(.*?)\\n---\", text, re.DOTALL)\n   if m:\n       for line in m.group(1).splitlines():\n           if \":\" in line:\n               k, v = line.split(\":\", 1)\n               meta[k.strip()] = v.strip().strip('\"').strip(\"'\")\n   return {\"path\": str(md), \"name\": meta.get(\"name\", p.name),\n           \"meta\": meta, \"found\": True}\ninspected = [parse_skill_md(pp) for pp in installed] if installed else []\nfor info in inspected:\n   print(f\"  • {info['name']}  ({'SKILL.md found' if info['found'] else 'no SKILL.md'})\")\n   desc = info[\"meta\"].get(\"description\", \"\")\n   if desc:\n       print(f\"      {textwrap.shorten(desc, 90, placeholder='...')}\")\nif not inspected:\n   print(\"  (nothing installed locally — likely no SDK/network; sections 2 & 7 still run)\")\n```\n\nWe inspect the installed skills by searching for their SKILL.md files and reading their metadata. We parse the front matter to extract useful information, such as the skill name and description. We then print a clean summary of each installed skill to understand what has been added locally.\n\n```\nbanner(\"5) Evaluate skills on 5 quality dimensions (quality gate)\")\nDIMS = [\"safety\", \"completeness\", \"executability\", \"maintainability\", \"cost_awareness\"]\nLEVEL_SCORE = {\"Excellent\": 4, \"Good\": 3, \"Fair\": 2, \"Poor\": 1, \"Bad\": 0}\ndef evaluate(target):\n   if USE_SDK and API_KEY:\n       try:\n           return client.evaluate(target=target)\n       except Exception as e:\n           print(f\"  evaluate failed for {target}: {e!r}\")\n   return None\ndef mock_eval(name):\n   import hashlib\n   h = int(hashlib.md5(name.encode()).hexdigest(), 16)\n   levels = [\"Excellent\", \"Good\", \"Fair\", \"Poor\"]\n   return {d: {\"level\": levels[(h >> (i * 3)) % 4], \"reason\": \"offline mock score\"}\n           for i, d in enumerate(DIMS)}\ndef gate_score(report):\n   tot = sum(LEVEL_SCORE.get(report.get(d, {}).get(\"level\", \"Fair\"), 2) for d in DIMS)\n   return tot / (len(DIMS) * 4)\nGATE_THRESHOLD = 0.55\ntargets = [s[\"skill_url\"] for s in (kw_hits + vec_hits) if s[\"skill_url\"]][:3] \\\n         or [i[\"name\"] for i in inspected] or [\"pdf-extractor\", \"chart-reader\", \"web-scraper\"]\npassed, scored = [], []\nfor t in targets:\n   rep = evaluate(t)\n   via = \"LLM\"\n   if rep is None:\n       rep, via = mock_eval(str(t)), \"mock\"\n   score = gate_score(rep)\n   scored.append((t, score, via))\n   flags = \" \".join(f\"{d[:4]}={rep.get(d,{}).get('level','?')[:4]}\" for d in DIMS)\n   status = \"PASS ✅\" if score >= GATE_THRESHOLD else \"FAIL ❌\"\n   print(f\"  [{via:4}] {status} score={score:.2f}  {textwrap.shorten(str(t),46,placeholder='...')}\")\n   print(f\"          {flags}\")\n   if score >= GATE_THRESHOLD:\n       passed.append(t)\nprint(f\"\\n{len(passed)}/{len(targets)} skills passed the quality gate (threshold={GATE_THRESHOLD}).\")\nbanner(\"6) Analyze relationships and draw the Skill Graph\")\ndef analyze(skills_dir):\n   if USE_SDK and API_KEY:\n       try:\n           return client.analyze(skills_dir=str(skills_dir))\n       except Exception as e:\n           print(f\"  analyze failed: {e!r}\")\n   return None\nrels = analyze(SKILLS_DIR)\nif not rels:\n   names = [i[\"name\"] for i in inspected] or [\"PDF_Parser\", \"Text_Summarizer\",\n                                              \"Chart_Reader\", \"Web_Scraper\"]\n   while len(names) < 4:\n       names.append(f\"Skill_{len(names)}\")\n   rels = [\n       {\"source\": names[0], \"type\": \"compose_with\", \"target\": names[1]},\n       {\"source\": names[2], \"type\": \"similar_to\",   \"target\": names[0]},\n       {\"source\": names[3], \"type\": \"depend_on\",    \"target\": names[1]},\n       {\"source\": names[1], \"type\": \"belong_to\",    \"target\": names[2]},\n   ]\n   print(\"  (using offline mock relationships — set API_KEY for real analysis)\")\nfor r in rels:\n   print(f\"  {r['source']} --[{r['type']}]--> {r['target']}\")\ntry:\n   import networkx as nx\n   import matplotlib.pyplot as plt\n   G = nx.DiGraph()\n   COLORS = {\"similar_to\": \"#4C9BE8\", \"belong_to\": \"#E8A14C\",\n             \"compose_with\": \"#6BBF59\", \"depend_on\": \"#D45D79\"}\n   for r in rels:\n       G.add_edge(r[\"source\"], r[\"target\"], type=r[\"type\"])\n   pos = nx.spring_layout(G, seed=42, k=1.2)\n   plt.figure(figsize=(9, 6))\n   nx.draw_networkx_nodes(G, pos, node_size=2200, node_color=\"#EDEDED\", edgecolors=\"#444\")\n   nx.draw_networkx_labels(G, pos, font_size=9)\n   for et, col in COLORS.items():\n       edges = [(u, v) for u, v, d in G.edges(data=True) if d[\"type\"] == et]\n       if edges:\n           nx.draw_networkx_edges(G, pos, edgelist=edges, edge_color=col,\n                                  width=2, arrows=True, arrowsize=18,\n                                  connectionstyle=\"arc3,rad=0.08\")\n   plt.legend(handles=[plt.Line2D([0], [0], color=c, lw=2, label=t)\n                       for t, c in COLORS.items()], loc=\"best\", fontsize=8)\n   plt.title(\"SkillNet — Skill Relationship Graph\")\n   plt.axis(\"off\"); plt.tight_layout()\n   plt.savefig(WORKDIR / \"skill_graph.png\", dpi=130)\n   plt.show()\n   print(f\"  graph saved -> {WORKDIR/'skill_graph.png'}\")\nexcept Exception as e:\n   print(f\"  graph drawing skipped: {e!r}\")\n```\n\nWe evaluate skills across five quality dimensions: safety, completeness, executability, maintainability, and cost awareness. We apply a quality gate to determine which skills meet a minimum score threshold, using mock scores when an API key is unavailable. We also analyze relationships between skills and visualize them as a Skill Graph using NetworkX and Matplotlib.\n\n```\nbanner(\"7) Skill-augmented agent planner\")\nGOAL = \"Analyze scRNA-seq data to find and validate cancer drug targets, then write a report\"\ndef llm_decompose(goal):\n   if API_KEY:\n       try:\n           payload = {\n               \"model\": MODEL,\n               \"messages\": [\n                   {\"role\": \"system\", \"content\":\n                    \"Decompose the user's goal into 3-6 short, ordered subtasks. \"\n                    \"Reply ONLY as a JSON array of strings, no prose, no markdown.\"},\n                   {\"role\": \"user\", \"content\": goal},\n               ],\n               \"temperature\": 0.2,\n           }\n           r = requests.post(f\"{BASE_URL}/chat/completions\",\n                             headers={\"Authorization\": f\"Bearer {API_KEY}\"},\n                             json=payload, timeout=60)\n           r.raise_for_status()\n           txt = r.json()[\"choices\"][0][\"message\"][\"content\"]\n           txt = re.sub(r\"^```(?:json)?|```$\", \"\", txt.strip()).strip()\n           subs = json.loads(txt)\n           if isinstance(subs, list) and subs:\n               return [str(x) for x in subs]\n       except Exception as e:\n           print(f\"  LLM decompose failed -> heuristic ({e!r})\")\n   return [\"acquire single-cell RNA-seq dataset\",\n           \"preprocess and cluster cells\",\n           \"identify candidate cancer target genes\",\n           \"validate targets against pathway database\",\n           \"generate a discovery report\"]\ndef keywords_for(subtask):\n   stop = {\"the\", \"and\", \"a\", \"to\", \"of\", \"from\", \"into\", \"for\", \"with\", \"then\", \"an\"}\n   toks = [w for w in re.findall(r\"[a-zA-Z\\-]+\", subtask.lower()) if w not in stop]\n   return \" \".join(toks[:4])\nsubtasks = llm_decompose(GOAL)\nprint(f\"GOAL: {GOAL}\\n\\nPLAN ({len(subtasks)} steps):\")\nplan = []\nfor i, st in enumerate(subtasks, 1):\n   q = keywords_for(st)\n   hits = search(q, mode=\"vector\", limit=2, threshold=0.6) or \\\n          search(q, mode=\"keyword\", limit=2)\n   best = hits[0] if hits else None\n   chosen = best[\"skill_name\"] if best else \"(no skill found — fallback to base model)\"\n   plan.append({\"step\": i, \"subtask\": st, \"query\": q, \"skill\": chosen})\n   print(f\"\\n  Step {i}: {st}\")\n   print(f\"     search('{q}') -> {chosen}\" + (f\"  ⭐{best['stars']}\" if best else \"\"))\nprint(\"\\nExecution order (assembled pipeline):\")\nprint(\"  \" + \"  ->  \".join(p[\"skill\"].split()[0] if p[\"skill\"][0] != \"(\" else \"base-model\"\n                           for p in plan))\nbanner(\"Tutorial complete\")\nprint(textwrap.dedent(f\"\"\"\n Recap:\n   • Search (keyword + vector) ............ ran via {'SDK' if USE_SDK else 'REST'}\n   • Install (GitHub -> local) ............ {len(installed)} skill(s)\n   • Inspect SKILL.md metadata ............ {len(inspected)} parsed\n   • Evaluate + quality gate .............. {len(passed)}/{len(targets)} passed {'(LLM)' if API_KEY else '(offline mock)'}\n   • Relationship graph ................... {len(rels)} edges -> skill_graph.png\n   • Agent planner ........................ {len(plan)} steps mapped to skills\n Docs: https://github.com/zjunlp/SkillNet\n\"\"\"))\n```\n\nWe build a skill-augmented agent planner around a complex scientific discovery goal. We decompose the goal into ordered subtasks, identify relevant skills for each step, and map those skills to an execution pipeline. We finish by printing a recap of the full workflow, including search, installation, inspection, evaluation, graph analysis, and planning.\n\nIn conclusion, we created a complete SkillNet workflow that moves beyond simple skill search and demonstrates how skills can support structured agentic systems. We saw how SkillNet helps us discover useful capabilities, evaluate their quality, understand their relationships, and connect them to real task planning. It also remains practical because it runs even without an API key by falling back to offline mock evaluations, while still allowing deeper LLM-powered analysis when credentials are available. Also, we used SkillNet as a foundation for building modular, skill-driven AI agents that can plan, select tools, and organize execution more intelligently.\n\nCheck out the ** Full Codes here. **Also, feel free to follow us on\n\n**and don’t forget to join our**[Twitter](https://x.com/intent/follow?screen_name=marktechpost)\n\n**and Subscribe to**\n\n[150k+ ML SubReddit](https://www.reddit.com/r/machinelearningnews/)**. Wait! are you on telegram?**\n\n[our Newsletter](https://www.aidevsignals.com/)\n\n[now you can join us on telegram as well.](https://t.me/machinelearningresearchnews)Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? [Connect with us](https://forms.gle/wbash1wF6efRj8G58)\n\nSana Hassan, a consulting intern at Marktechpost and dual-degree student at IIT Madras, is passionate about applying technology and AI to address real-world challenges. With a keen interest in solving practical problems, he brings a fresh perspective to the intersection of AI and real-life solutions.\n\n- Sana Hassan\n- Sana Hassan\n- Sana Hassan\n- Sana Hassan", "url": "https://wpnews.pro/news/build-skill-augmented-ai-agents-with-skillnet-for-search-evaluation-graph-and", "canonical_source": "https://www.marktechpost.com/2026/05/30/build-skill-augmented-ai-agents-with-skillnet-for-search-evaluation-graph-analysis-and-task-planning/", "published_at": "2026-05-31 01:28:04+00:00", "updated_at": "2026-05-31 01:56:43.105784+00:00", "lang": "en", "topics": ["ai-agents", "artificial-intelligence", "ai-tools", "natural-language-processing", "ai-research"], "entities": ["SkillNet", "GitHub", "OpenAI", "GPT-4o"], "alternates": {"html": "https://wpnews.pro/news/build-skill-augmented-ai-agents-with-skillnet-for-search-evaluation-graph-and", "markdown": "https://wpnews.pro/news/build-skill-augmented-ai-agents-with-skillnet-for-search-evaluation-graph-and.md", "text": "https://wpnews.pro/news/build-skill-augmented-ai-agents-with-skillnet-for-search-evaluation-graph-and.txt", "jsonld": "https://wpnews.pro/news/build-skill-augmented-ai-agents-with-skillnet-for-search-evaluation-graph-and.jsonld"}}