LangChain Skills 模式实战:构建按需加载知识的 SQL 助手

2/13/2026
5 min read

在先前的文章中,我们探讨了如何通过 Deep Agents CLI 模拟 Deep Agent 使用 Skills 的模式。如今,LangChain 已原生支持这一特性,极大地简化了开发流程。本文将带领大家深入体验这一功能,构建一个更智能的 SQL 助手。

构建复杂的 AI Agent 时,开发者往往陷入两难境地:是将所有上下文(数据库表结构、API 文档、业务规则)一次性注入 System Prompt,导致上下文窗口(Context Window)溢出且分散模型注意力?还是选择成本高昂的频繁微调(Fine-tuning)?

**Skills 模式(Skills Pattern)**提供了一条优雅的中间路线。它通过动态加载所需知识,实现了上下文的高效利用。LangChain 对此模式的原生支持,意味着我们可以更轻松地构建具备“按需学习”能力的 Agent。

本文将结合官方文档 Build a SQL assistant with on-demand skills,引导读者从零开始,构建一个支持“按需加载知识”的 SQL Assistant。

1. 核心概念:为何选择 Skills 模式?

传统 SQL Agent 的局限性

在传统的 SQL Agent 架构中,我们通常需要在 System Prompt 中提供完整的 Database Schema。随着业务发展,当表数量扩展到数百张时,这种方式会带来显著问题:

  • Token 消耗巨大:每次对话都携带大量无关的表结构,造成资源浪费。

  • 幻觉风险增加:过多的无关干扰信息会降低模型的推理准确性。

  • 维护困难:所有业务线的知识紧密耦合,难以独立迭代。

Skills 模式:基于渐进式披露的解决方案

Skills 模式基于**渐进式披露(Progressive Disclosure)**原则,将知识获取过程分层处理:

  • Agent 初始状态:仅掌握有哪些“技能”(Skills)及其简要描述(Description),保持轻量级。

  • 运行时加载:当面对具体问题(如“查询库存”)时,Agent 主动调用工具(load_skill)加载该技能详细的上下文(Schema + Prompt)。

  • 执行任务:基于加载的精确上下文,执行具体的任务(如编写并执行 SQL)。

这种模式有效支持了无限扩展团队解耦,使 Agent 能够适应日益复杂的业务场景。

2. 系统架构设计

本实战项目将构建一个包含两个核心 Skills 的 SQL Assistant,以演示该模式的实际应用:

  • Sales Analytics(销售分析):负责sales_data表,处理收入统计、订单趋势分析等。

  • Inventory Management(库存管理):负责inventory_items表,处理库存水平监控、位置查询等。

3. 开发环境搭建

本项目采用 Pythonuv进行高效的依赖管理。

核心依赖安装

uv add langchain langchain-openai langgraph psycopg2-binary python-dotenv langchain-community

PostgreSQL 环境配置

本地启动一个 Postgres 实例,并创建agent_platform数据库。我们提供了setup_db.py脚本来自动初始化表结构和测试数据(详见文末源码)。

4. 核心实现步骤详解### Skref eitt: Skilgreina sviðshæfni (Þekkingin)

Við skilgreinum hæfni sem orðabókarskipulag, sem líkir eftir ferlinu við að hlaða úr skráakerfi eða gagnagrunni. Vinsamlegast athugið að greina á milli description (notað af umboðsmanni til að taka ákvarðanir um val) og content (raunverulegt hlaðið nákvæmt samhengi).

SKILLS = {"sales_analytics": {"description":"Gagnlegt til að greina sölutekjur, þróun...","content":"""... Tafla Skema: sales_data ..."" },"inventory_management": {"description":"Gagnlegt til að athuga birgðastöðu...","content":"""... Tafla Skema: inventory_items ..."" }}

Skref tvö: Útfæra kjarnaverkfæri (Hæfileikarnir)

Umboðsmaðurinn treystir á tvö lykilverkfæri til að ljúka verkefnum:

  • load_skill(skill_name)**: Hleður upplýsingum um tilgreinda hæfni á virkan hátt á keyrslutíma. **

  • run_sql_query(query)**: Framkvæmir sérstakar SQL skipanir. **

Skref þrjú: Skipuleggja rökfræði umboðsmanns (Heilinn)

Notaðu LangGraph til að byggja upp ReAct umboðsmann. System Prompt gegnir lykilhlutverki hér, þar sem það leiðbeinir umboðsmanninum um að fylgja stranglega Identify -> Load -> Query staðlaðri vinnureglu (SOP).

system_prompt ="""1. Identify the relevant skill.2. Use 'load_skill' to get schema.3. Write and execute SQL using 'run_sql_query'....Do not guess table names. Always load the skill first.""

5. Staðfesting á keyrsluáhrifum

Með því að keyra test_agent.py prófuðum við fyrirspurnir á tveimur mismunandi sviðum, sölu og birgða. Eftirfarandi eru raunverulegar úttaksskrár frá stjórnborðinu, sem sýna hvernig umboðsmaðurinn hleður hæfni á virkan hátt í samræmi við spurninguna:

Testing Sales Query...Agent calling tools: [{'name': 'load_skill', 'args': {'skill_name': 'sales_analytics'}, 'id': 'call_f270d76b7ce4404cb5f61bf2', 'type': 'tool_call'}]Tool output:You are a Sales Analytics Expert.You have access to the 'sales_data' table.Table Schema:- id: integer...Agent calling tools: [{'name': 'run_sql_query', 'args': {'query': 'SELECT SUM(amount) as total_revenue FROM sales_data;'}, 'id': 'call_b4f3e686cc7f4f22b3bb9ea7', 'type': 'tool_call'}]Tool output: [(Decimal('730.50'),)]...Agent response: The total revenue is $730.50.Testing Inventory Query...Agent calling tools: [{'name': 'load_skill', 'args': {'skill_name': 'inventory_management'}, 'id': 'call_18c823b2d5064e95a0cfe2e3', 'type': 'tool_call'}]Tool output:You are an Inventory Management Expert.You have access to the 'inventory_items' table.Table Schema...Agent calling tools: [{'name': 'run_sql_query', 'args': {'query': "SELECT warehouse_location FROM inventory_items WHERE product_name = 'Laptop';"}, 'id': 'call_647ee3a444804bd98a045f00', 'type': 'tool_call'}]Tool output: [('Warehouse A',)]...Agent response: The Laptop is located in **Warehouse A**.## 6. Heildarvi\u00f6ruheimildaskr\u00e1\n\nH\u00e9r a\u00f0 ne\u00f0an er heildarvi\u00f6ruheimildaskr\u00e1 verkefnisins, sem inniheldur upphafsskr\u00e1 gagnagrunnsins og Agent a\u00f0alforriti\u00f0.\n\n### 1. Upphafstilling gagnagrunns (setup_db.py)\n\n`importpsycopg2frompsycopg2.extensionsimportISOLATION_LEVEL_AUTOCOMMITimportosfromdotenvimportload_dotenvload_dotenv()# Vinsamlegast tryggi\u00f0 a\u00f0 stilla uppl\u00fdsingar um gagnagrunnstengingu \u00ed .env skr\u00e1nniDB_HOST = os.getenv(### 2. Agent 主程序 (main.py)

`importosfromtypingimportAnnotated, Literal, TypedDict, Union, Dictfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromlangchain_core.toolsimporttoolfromlangchain_core.messagesimportSystemMessage, HumanMessage, AIMessage, ToolMessagefromlangchain_community.utilitiesimportSQLDatabasefromlangchain_community.agent_toolkitsimportSQLDatabaseToolkitfromlanggraph.graphimportStateGraph, START, END, MessagesStatefromlanggraph.prebuiltimportToolNode, tools_conditionload_dotenv()# --- Configuration ---BASE_URL = os.getenv("BASIC_MODEL_BASE_URL")API_KEY = os.getenv("BASIC_MODEL_API_KEY")MODEL_NAME = os.getenv("BASIC_MODEL_MODEL")DB_URI =f"postgresql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"# --- Database Setup ---db = SQLDatabase.from_uri(DB_URI)# --- Skills Definition ---SKILLS: Dict[str, Dict[str, str]] = {"sales_analytics": {"description":"Gagnlegt til að greina sölutekjur, þróun og svæðisbundna frammistöðu.","content":"""Þú ert sérfræðingur í sölugreiningu.Þú hefur aðgang að 'sales_data' töflunni.Tafla Skema:- id: heiltala (aðallykill)- transaction_date: dagsetning- product_id: varchar(50)- amount: decimal(10, 2)- region: varchar(50)Algengar fyrirspurnir:- Heildartekjur: SUM(amount)- Tekjur eftir svæðum: GROUP BY region- Söluþróun: GROUP BY transaction_date""" },"inventory_management": {"description":"Gagnlegt til að athuga birgðastöðu, vörustaðsetningar og vöruhúsastjórnun.","content":"""Þú ert sérfræðingur í birgðastjórnun.Þú hefur aðgang að 'inventory_item table.Table Schema:- id: integer (aðallykill)- product_id: varchar(50)- product_name: varchar(100)- stock_count: integer- warehouse_location: varchar(50)Algengar fyrirspurnir:- Athuga birgðir: WHERE product_name = '...'- Lítið birgðamagn: WHERE stock_count < threshold""" }}# --- Tools ---@tooldefload_skill(skill_name: str)-> str:""" Hlaða nákvæma hvatningu og skema fyrir ákveðna færni. Tiltæk færni: - sales_analytics: Fyrir sölu-, tekju- og viðskiptagreiningu. - inventory_management: Fyrir birgða-, vöru- og vöruhúsafyrirspurnir. """ skill = SKILLS.get(skill_name)ifnotskill:returnf"Villa: Færnin '{skill_name}' fannst ekki. Tiltæk færni:{list(SKILLS.keys())}"returnskill["content"]@tooldefrun_sql_query(query: str)-> str:""" Framkvæma SQL fyrirspurn gagnvart gagnagrunninum. Notaðu aðeins þetta tól EFTIR að hafa hlaðið viðeigandi færni til að skilja skemað. """try:returndb.run(query)exceptExceptionase:returnf"Villa við framkvæmd SQL:{e}"@tooldeflist_tables()-> str:"""Listi yfir öll tiltæk töflur í gagnagrunninum."""returnstr(db.get_usable_table_names())tools = [load_skill, run_sql_query, list_tables]# --- Uppsetning umboðsmanns ---llm = ChatOpenAI( base_url=BASE_URL, api_key=API_KEY, model=MODEL_NAME, temperature=0)llm_with_tools = llm.bind_tools(tools)# --- Grafskilgreining ---classAgentState(MessagesState):# Við getum bætt við sérsniðnu ástandi ef þörf krefur, en MessagesState er nóg fyrir einfalt spjallpassdefagent_node(state: AgentState): messages = state["messages"] response = llm_with_tools.invoke(messages)return{"messages": [response]}workflow = StateGraph(AgentState)workflow.add_node("agent", agent_node)workflow.add_node("tools", ToolNode(tools))workflow.add_edge(START,"agent")workflow.add_conditional_edges("agent", tools_condition)workflow.add_edge("tools","agent")app = workflow.compile()# --- Aðal keyrsla ---if__name__ =="main": system_prompt ="""Þú ert gagnlegur SQL aðstoðarmaður.Þú hefur aðgang að sérhæfðri færni sem inniheldur gagnagrunnsskýrslur og lénisþekkingu.Til að svara spurningu notanda:1. Auðkenndu viðeigandi færni (sales_analytics eða inventory_management).2. Notaðu 'load_skill' tólið til að fá skýrsluna og leiðbeiningarnar.3. Byggt á hlaðinni færni, skrifaðu og keyrðu SQL fyrirspurn með 'run_sql_query'.4. Svaraðu spurningu notandans út frá niðurstöðum fyrirspurnarinnar.Ekki giska á töflunöfn. Hladdu alltaf færninni fyrst.""" print("SQL aðstoðarmaður ræstur. Sláðu inn 'quit' til að hætta.") print("-"*50) messages = [SystemMessage(content=system_prompt)]# Forhita tengingarskoðunreynt: print(f"Tengst gagnagrunni:{DB_URI.split('@')[-1]}")nema Exceptionase: print(f"Viðvörun um gagnagrunnstengingu:{e}")meðan satt:reynt: user_input = input("Notandi: ")ef user_input.lower() í ["quit","exit"]:brjóttu messages.append(HumanMessage(content=user_input))# Streymdu keyrslunni print("Umboðsmaður: ", end="", flush=True) final_response =Nonefyrir event í app.stream({"messages": messages}, stream_mode="values"):# Í 'values' ham fáum við fullt ástand. Við viljum bara sjá síðustu skilaboð ef þau eru ný. last_message = event["messages"][-1]# Uppfærðu skilaboðasögu okkar með nýjasta ástandinupass# Eftir að streymi lýkur hefur síðasta ástandið endanlegt svar final_state = app.invoke({"messages": messages})

last_msg = final_state["messages"][-1]

if isinstance(last_msg, AIMessage):

print(last_msg.content)

messages = final_state["messages"]

Uppfærðu sögu

print("-"*50)

except Exception as e:

print(f"\nVilla: {e}")

break

Published in Technology

You Might Also Like