1. 项目概述一个面向医疗健康领域的智能代理技能最近在探索AI智能体Agent的实际落地场景尤其是在垂直领域如何让大语言模型LLM真正“干点实事”。我发现了一个挺有意思的开源项目名字叫alexpolonsky/agent-skill-maccabi-pharm-search。光看这个标题就能拆解出几个关键信息这是一个“Agent Skill”智能体技能核心功能是“Pharm Search”药品搜索而“Maccabi”则指向了一个特定的应用场景——以色列的Maccabi医疗服务机构。简单来说这个项目旨在构建一个能够查询药品信息的智能体技能很可能集成在某个聊天机器人或自动化流程中为用户提供便捷的药品查询服务。这背后反映了一个明确的趋势通用大模型在专业领域的知识深度和准确性上存在局限特别是在医药这种对信息精确度要求极高的领域。一个错误的药品信息可能导致严重后果。因此开发专用的“技能”Skill来增强智能体在特定领域的专业能力成为了一个非常务实的技术方向。这个项目就是一个典型的案例它尝试将药品数据库与自然语言理解能力结合让用户能用最自然的方式比如问“我感冒了吃什么药好”或“阿莫西林和布洛芬能一起吃吗”获取结构化、可靠的药品信息。对于开发者、医疗健康领域的从业者或者对AI应用落地感兴趣的朋友来说这个项目提供了一个绝佳的学习样板。它涉及了从数据处理、API设计、到与大模型如OpenAI GPT, Anthropic Claude等集成的完整链路。通过剖析它我们不仅能学会如何构建一个垂直领域的AI技能更能深入理解在实际业务中如何平衡技术的灵活性、数据的准确性以及系统的安全性。2. 核心架构与设计思路拆解2.1 技能型智能体的设计范式在深入代码之前我们需要理解“Agent Skill”在这个上下文中的含义。它不是一个独立的、庞大的AI系统而是一个模块化的、可插拔的功能单元。你可以把它想象成智能手机上的一个“小程序”或“快捷指令”。主智能体比如一个医疗咨询聊天机器人在判断用户意图涉及药品查询时就会调用这个“药品搜索技能”。这种设计有几个显著优势。首先是解耦与复用。药品搜索逻辑被封装成一个独立的技能任何需要此功能的智能体都可以调用它无需重复开发。其次是职责清晰。技能只负责一件事根据输入查询药品信息并返回结果。用户意图识别、对话管理、结果呈现等由主智能体或其他技能负责。最后是易于维护和更新。药品数据库或搜索算法变更时只需更新这个技能模块不影响其他功能。maccabi-pharm-search这个技能其核心设计思路必然是接收自然语言查询 - 解析查询意图并提取关键参数 - 查询权威药品数据库 - 格式化返回结果。这里的难点在于第二步和第三步的衔接如何将用户模糊的、非结构化的描述精准地映射到数据库的查询字段上。2.2 技术栈选型与数据源考量从项目名称和常见实践推断该技能的技术栈很可能包含以下几个部分后端框架/运行时可能是基于Python的FastAPI或Flask来构建技能的服务端点API这是目前构建AI技能微服务最主流、最轻量的选择。大模型集成用于查询的意图解析和信息提取。很可能会集成OpenAI的GPT系列或开源的Llama等模型通过精心设计的提示词Prompt让模型理解医疗查询并输出结构化的搜索参数如药品通用名、商品名、剂量、厂商等。数据层核心是“Maccabi”相关的药品数据库。这可能是通过合法途径获取的Maccabi医疗服务机构内部的药品处方集数据或者是整合了公共药品数据库如以色列的官方药品数据库并进行了适配。数据可能以SQL数据库、Elasticsearch索引或简单的JSON文件形式存在关键在于支持高效的检索。工具调用Tool Calling这是实现技能的关键机制。主智能体通过标准的工具调用协议如OpenAI的Function Calling或ReAct范式中的工具使用来触发此技能。技能需要对外暴露一个清晰的工具定义名称、描述、参数列表。注意处理医疗健康数据尤其是涉及具体医疗机构的数据合规性与隐私安全是生命线。任何此类项目都必须确保数据来源合法、使用符合相关法规如HIPAA、GDPR等并在设计上就考虑数据脱敏、访问控制和安全审计。开源项目通常会使用模拟数据或公开数据集来演示架构实际商用必须解决数据授权问题。2.3 查询意图解析的挑战与策略用户不会说“请查询数据库中药品通用名包含‘Amoxicillin’、剂型为‘胶囊’、规格为‘500mg’的记录”。他们更可能说“我喉咙痛医生开了阿莫西林胶囊500毫克的这是什么药”或者“Maccabi里有没有治疗高血压的替代药”因此技能的核心智能在于意图解析模块。这通常通过与大模型协作完成但绝非简单地将用户问题扔给模型。需要设计一个强大的提示词工程Prompt Engineering流程系统角色设定明确告诉模型你是一个专业的药品信息查询助手只能基于已知数据库回答问题对于不确定或数据库外的信息要明确告知。查询参数标准化定义好输出格式。例如要求模型始终输出一个JSON对象包含generic_name通用名、brand_name商品名、strength规格、form剂型、manufacturer生产商等字段。对于用户未提及的字段输出null或空字符串。查询扩展与纠错考虑到用户可能使用别名、缩写或拼写错误模型或后续处理逻辑需要具备一定的纠错和同义词扩展能力例如“扑热息痛”对应“对乙酰氨基酚”“APAP”是其缩写。安全过滤在解析意图时就要加入安全层。例如模型应拒绝回答“哪种安眠药效果最强且不易上瘾”这类可能引发滥用风险的询问而是引导用户咨询专业医师。这个解析过程的质量直接决定了后续数据库查询的准确性和用户体验。3. 核心模块实现与代码级解析3.1 API服务端点的构建技能需要以一个HTTP API端点的形式存在供主智能体调用。我们以Python FastAPI为例勾勒其核心结构。from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import Optional, List import logging # 定义请求和响应模型 class DrugQueryRequest(BaseModel): user_query: str # 用户的自然语言查询 user_context: Optional[dict] None # 可选的用户上下文如过敏史需脱敏处理 class DrugQueryResponse(BaseModel): success: bool data: Optional[List[dict]] None # 查询到的药品列表 error_message: Optional[str] None suggested_actions: Optional[List[str]] None # 给用户的建议如“咨询药师” app FastAPI(titleMaccabi Drug Search Skill API) app.post(/search, response_modelDrugQueryResponse) async def search_drugs(request: DrugQueryRequest): 药品搜索技能的主入口点。 try: # 1. 意图解析 search_params await parse_query_intent(request.user_query) # 2. 安全检查例如过滤危险组合查询 if not is_query_safe(search_params): return DrugQueryResponse( successFalse, error_message您的查询涉及医疗建议为安全起见请咨询专业医生或药师。, suggested_actions[联系Maccabi药师] ) # 3. 数据库查询 results query_drug_database(search_params) # 4. 结果格式化与后处理 formatted_results format_results(results, request.user_context) return DrugQueryResponse( successTrue, dataformatted_results, suggested_actions[结果仅供参考用药请遵医嘱] ) except Exception as e: logging.error(fDrug search failed: {e}) return DrugQueryResponse( successFalse, error_message系统暂时无法处理您的请求请稍后再试或联系客服。 )这个端点清晰定义了输入输出并包含了基本的错误处理和安全检查流程。3.2 意图解析器的实现parse_query_intent函数是大脑。以下是其实现的一个简化示例使用OpenAI APIimport openai import json import os openai.api_key os.getenv(OPENAI_API_KEY) async def parse_query_intent(user_query: str) - dict: prompt f 你是一个专业的药品信息查询系统解析器。你的任务是将用户的自然语言查询转化为结构化的药品搜索参数。 用户查询{user_query} 请根据查询填充以下JSON对象。如果用户没有明确提及某个字段请将其设置为null。请确保药品名称的拼写尽量规范。 {{ generic_name: 药品通用名 (例如: amoxicillin), brand_name: 商品名 (例如: Moxatag), strength: 规格 (例如: 500 mg), form: 剂型 (例如: tablet, capsule, syrup), manufacturer: 生产商 (例如: Teva), purpose: 主要用途/适应症 (例如: for bacterial infection) }} 只输出JSON对象不要有任何其他解释。 try: response await openai.ChatCompletion.acreate( modelgpt-3.5-turbo, # 或 gpt-4 以获得更好效果 messages[{role: user, content: prompt}], temperature0.1, # 低温度保证输出稳定性 ) result_text response.choices[0].message.content.strip() # 尝试解析JSON search_params json.loads(result_text) return search_params except json.JSONDecodeError: logging.warning(fFailed to parse LLM output as JSON: {result_text}) # 降级策略尝试提取关键词进行模糊搜索 return {keyword: extract_fallback_keywords(user_query)} except Exception as e: logging.error(fIntent parsing API call failed: {e}) raise实操心得在实际部署中直接为每次查询调用大模型成本高、延迟大。一个优化策略是引入缓存层。对解析后的search_paramsJSON字符串进行哈希作为缓存键。对于相同或相似的查询直接使用缓存结果可以极大提升响应速度并降低成本。同时可以维护一个常见的“查询-参数”映射表对于高频问题如“什么是阿司匹林”走本地映射完全绕过模型调用。3.3 数据库查询层与结果格式化假设我们有一个SQLite数据库drugs.db其中包含maccabi_drugs表。import sqlite3 from typing import Dict, List def query_drug_database(params: Dict) - List[Dict]: conn sqlite3.connect(drugs.db) conn.row_factory sqlite3.Row # 以字典形式返回行 cursor conn.cursor() query_parts [] query_values [] # 根据解析出的参数动态构建SQL查询 if params.get(generic_name): query_parts.append(generic_name LIKE ?) query_values.append(f%{params[generic_name]}%) if params.get(brand_name): query_parts.append(brand_name LIKE ?) query_values.append(f%{params[brand_name]}%) if params.get(strength): # 这里需要更复杂的处理来匹配“500 mg”和“500mg” normalized_strength normalize_strength(params[strength]) query_parts.append(strength ?) query_values.append(normalized_strength) # ... 类似处理其他字段 if not query_parts: # 如果没有任何明确参数使用降级的关键词搜索 if params.get(keyword): keyword params[keyword] sql SELECT * FROM maccabi_drugs WHERE generic_name LIKE ? OR brand_name LIKE ? OR purpose LIKE ? LIMIT 10 cursor.execute(sql, (f%{keyword}%, f%{keyword}%, f%{keyword}%)) else: return [] else: sql SELECT * FROM maccabi_drugs WHERE AND .join(query_parts) LIMIT 15 cursor.execute(sql, query_values) rows cursor.fetchall() conn.close() return [dict(row) for row in rows] def format_results(results: List[Dict], user_context: Optional[Dict]) - List[Dict]: 格式化结果可能根据用户上下文如过敏史进行过滤或标记 formatted [] for drug in results: # 基础信息 item { name: f{drug.get(brand_name, )} ({drug.get(generic_name, )}), strength_form: f{drug.get(strength, N/A)} {drug.get(form, )}, manufacturer: drug.get(manufacturer), purpose: drug.get(purpose), common_side_effects: drug.get(side_effects), # **关键安全信息** is_contraindicated: False, warning: } # **安全检查如果提供了用户过敏史进行匹配** if user_context and allergies in user_context: allergies user_context[allergies] drug_ingredients drug.get(active_ingredients, ).lower() if any(allergy.lower() in drug_ingredients for allergy in allergies): item[is_contraindicated] True item[warning] 警告此药品含有您可能过敏的成分。 formatted.append(item) return formatted这个查询层展示了如何将解析出的结构化参数转换为数据库查询。format_results函数则体现了技能的价值升华——它不仅返回数据还基于有限的用户上下文提供了初步的安全警示。4. 技能集成与工具定义要让主智能体如使用LangChain、AutoGen或自定义框架构建的Agent能调用这个技能我们需要定义一个标准的“工具”Tool。# 假设主智能体使用类似LangChain的框架 from langchain.tools import Tool drug_search_tool Tool( namesearch_maccabi_drugs, description根据描述搜索Maccabi药品数据库中的药品信息。输入应为用户的自然语言查询例如‘治疗高血压的药有哪些’或‘阿莫西林500mg胶囊的信息’。, funclambda query: call_skill_api(query), # 这里func需要适配异步或同步调用 coroutinelambda query: acall_skill_api(query), # 异步版本 return_directFalse, # 通常为False让Agent决定如何呈现结果 ) # 或者更规范地使用OpenAI的Function Calling定义 drug_search_function { name: search_maccabi_drugs, description: 在Maccabi药品数据库中搜索药品的详细信息。, parameters: { type: object, properties: { user_query: { type: string, description: 用户关于药品的自然语言查询。 } }, required: [user_query] } }主智能体在运行时会根据与用户的对话历史判断是否需要调用search_maccabi_drugs这个工具。一旦决定调用就会将用户的当前问题作为user_query参数传递给我们的技能API然后将API返回的结构化结果用自然语言组织后回复给用户。5. 部署、监控与安全考量5.1 部署策略这样一个技能理想的部署方式是作为独立的微服务例如使用Docker容器化。# Dockerfile FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]使用Kubernetes或简单的云服务器进行部署并为其配置好健康检查端点如/health。主智能体服务通过内部网络或API网关调用此技能。5.2 监控与日志医疗健康类应用对稳定性和可追溯性要求极高。必须实施完善的监控。性能监控记录每个API调用的延迟、成功率。如果使用大模型特别要监控其token消耗和响应时间。业务日志详细记录每一次查询的输入脱敏后、解析出的参数、返回的结果数量。这对于调试和优化意图解析器至关重要。错误监控集中收集所有异常特别是数据库查询错误和大模型API错误。审计日志出于合规要求可能需要记录谁在什么时候查询了什么需严格脱敏仅记录操作元数据。5.3 安全与合规加固这是此类项目的重中之重远超技术实现本身。输入验证与消毒对所有输入进行严格的验证防止SQL注入、Prompt注入等攻击。例如虽然参数由模型解析但仍需对最终传入数据库查询的参数进行长度、字符类型检查。输出过滤与免责声明所有返回给用户的信息都必须包含明确的免责声明例如“此信息来源于公开数据库仅供参考不能替代专业医疗建议。用药前请咨询医生或药师并仔细阅读药品说明书。” 对于剂量、用法等关键信息技能应避免给出具体建议只呈现药品说明书中的客观事实。访问控制技能API不应公开暴露。应通过API密钥、JWT令牌或服务网格策略确保只有受信任的主智能体服务可以调用。数据脱敏与匿名化日志和监控数据中不得包含任何个人身份信息PII或受保护的健康信息PHI。所有用户相关的上下文如假设的过敏史在传输和存储时都必须脱敏。合规性文档确保有清晰的数据来源证明、数据处理协议和隐私政策。6. 常见问题、优化方向与扩展思考6.1 开发与调试中的常见问题意图解析不准这是最常见的问题。用户说“头疼药”模型可能解析出“paracetamol”但数据库里可能叫“acetaminophen”对乙酰氨基酚的另一种叫法。解决建立同义词词典。在查询数据库前将解析出的通用名通过一个本地同义词映射表进行扩展。例如将“paracetamol”也映射到“acetaminophen”进行查询。同时优化Prompt明确要求模型输出“最可能出现在专业数据库中的标准名称”。数据库查询无结果解决实现查询松弛策略。首先进行精确/主要字段查询若无结果则逐步放宽条件例如忽略剂型、只匹配通用名再若无果则进行关键词全文搜索。并在最终回复中友好提示“未找到完全匹配的药品以下是与‘[用户关键词]’相关的药品供您参考。”大模型响应慢或成本高解决如前所述引入缓存。对于解析意图可以使用查询文本的语义哈希如SimHash作为键缓存解析出的参数。对于最终结果也可以在一定时间内缓存。另外可以考虑使用更小、更快的本地模型如经过微调的BERT类模型来处理简单的、模式固定的查询将复杂查询才交给大模型。技能被滥用用户可能询问如何滥用药物或制作非法药品。解决在意图解析前和后设置双重安全过滤。解析前可以用一个简单的分类器或关键词列表过滤明显恶意查询。解析后对结构化的参数进行检查例如是否在查询某些受控物质的组合一旦发现风险立即终止并返回标准安全提示。6.2 性能与体验优化异步处理从意图解析到数据库查询可能涉及多个网络I/O操作调用大模型API、查询远程数据库。使用异步框架如asyncioaiohttp/asyncpg可以显著提高并发处理能力避免在I/O等待时阻塞。结果排序与摘要当返回药品列表较长时提供智能排序如按相关性、按常用程度至关重要。甚至可以让大模型对结果进行一句话摘要例如“共找到15种降压药主要包括血管紧张素转化酶抑制剂如雷米普利、钙通道阻滞剂如氨氯地平等类别。”多轮对话支持当前技能是单次查询。更高级的集成是支持多轮对话中的指代消解。例如用户先问“降压药有哪些”然后问“第一种的副作用是什么”。这需要技能能接收并处理简单的对话历史或者由主智能体维护上下文并将“第一种”解析为具体的药品名后再调用技能。6.3 技能的可扩展性agent-skill-maccabi-pharm-search提供了一个完美的模板。其架构可以轻松复用到其他垂直领域变更数据源将药品数据库换成“医疗设备数据库”、“临床指南数据库”或“保险条款数据库”就能快速创建“医疗设备查询技能”、“临床决策支持技能”、“保险理赔查询技能”。增强功能在当前技能基础上可以增加药品比价、药品相互作用检查需要更复杂的知识图谱、附近药房库存查询集成地理位置API等子功能。多模态扩展除了文本查询未来可以支持用户上传药品图片通过视觉模型识别药盒然后调用本技能查询详细信息。这个项目的真正价值在于它展示了一种将专业领域知识、结构化数据与大型语言模型的自然语言能力相结合的可复现模式。它没有追求做一个“万能医疗AI”而是脚踏实地地解决一个具体问题并通过清晰的接口融入更大的智能体生态。对于想要在AI应用浪潮中寻找务实切入点的团队和个人来说深入研究和实践这样一个项目远比空谈概念要有益得多。