LangChain与Azure Functions集成:构建企业级AI API服务
1. 项目概述当LangChain遇上Azure Functions最近在折腾一个有意思的东西把LangChain和Azure OpenAI的能力封装成一个可以通过HTTP直接调用的API服务。核心想法很简单但实现起来有不少门道。我们手头这个Azure-Samples/function-python-ai-langchain项目就是一个绝佳的起点。它本质上是一个用Python v2编程模型写的Azure Functions应用专门用来接收一个人类提示词prompt然后利用LangChain的链Chain能力结合预定义的模板调用Azure OpenAI的模型比如GPT-3.5-Turbo来生成智能回复。这玩意儿适合谁呢如果你正在构建需要集成大语言模型LLM能力的后端服务比如智能客服接口、内容生成引擎、或者数据分析助手但又不想从头搭建一套复杂的Web框架和模型调用逻辑那么这个模板就非常对口。它帮你把LangChain的灵活性与Azure Functions的无服务器Serverless特性、以及Azure OpenAI的企业级稳定性打包在了一起。你只需要关注你的业务逻辑和提示词工程部署和扩展的事情交给云平台就行。2. 核心架构与设计思路拆解2.1 为什么选择Azure Functions LangChain这个组合这个技术选型背后有很强的实用主义考量。首先Azure Functions作为无服务器计算服务完美契合了AI任务“按需调用、突发性强”的特点。你不需要维护一台24小时运行的服务器只有在HTTP请求触发时函数才会执行并计费。这对于初期验证、流量波动的场景成本控制和运维复杂度都大大降低。其次LangChain是一个用于开发由语言模型驱动的应用程序的框架。它的价值在于提供了大量的“组件”如提示词模板、链、代理、记忆等和“工具”让你能像搭积木一样构建复杂的AI应用逻辑而不是每次都从零开始写HTTP请求和解析JSON。在这个项目里我们主要用到了它的AzureChatOpenAI封装和PromptTemplate。最后Azure OpenAI提供了与企业级Azure服务深度集成的大模型访问。相比直接使用OpenAI的公开APIAzure OpenAI在数据隐私、合规性、网络延迟如果你的其他服务也在Azure上以及与企业现有身份认证体系如Microsoft Entra ID原名Azure AD的集成上有天然优势。项目默认采用无密钥Keyless的Entra ID身份认证这比管理API密钥更安全。2.2 项目工作流解析整个应用的工作流可以清晰地分为几个阶段HTTP触发用户通过POST /api/ask发送一个包含prompt字段的JSON请求。环境初始化Azure Functions运行时加载local.settings.json本地或应用程序设置云端中的配置特别是AZURE_OPENAI_ENDPOINT和AZURE_OPENAI_CHATGPT_DEPLOYMENT。LangChain组件构建根据配置初始化AzureChatOpenAI实例连接到指定的Azure OpenAI模型部署。使用PromptTemplate定义一个对话模板将用户输入的原始提示词嵌入到一个结构更完整、引导性更强的对话上下文中。链式执行与调用将格式化后的提示词传递给LLM并获取生成的回复。HTTP响应将LLM的回复内容包装成JSON返回给客户端。这个设计的巧妙之处在于function_app.py中的main函数即HTTP触发器函数非常简洁复杂的LangChain配置和初始化逻辑被封装在init函数中确保了代码的清晰度和可维护性。3. 本地开发环境搭建与配置详解3.1 前期准备工具链安装要跑通这个项目你的本地机器需要准备好以下几样东西这不仅是运行条件也是理解现代云原生AI开发的基础。Python 3.8这是LangChain和Azure Functions Python worker的运行基础。建议使用Python 3.9或3.10以获得最佳的兼容性。可以使用pyenv或conda来管理多个Python版本。Azure Functions Core Tools这是本地运行、调试和部署Azure Functions的核心命令行工具。安装后你才能使用func start命令在本地启动一个模拟的Functions运行时环境。Azure Developer CLI (azd)这是一个提升Azure开发体验的整合工具。它通过一个azure.yaml配置文件可以一键式地为你预配Provision所有需要的Azure资源如Azure OpenAI资源、API管理、应用服务计划等并完成代码的部署。对于这个项目它是创建Azure OpenAI资源的推荐方式。注意azd provision命令会真正在你的Azure订阅中创建资源可能会产生费用。请确保你在正确的订阅下操作并了解相关资源的定价。3.2 关键一步资源预配与环境变量获取运行azd provision后CLI会执行Bicep或ARM模板在Azure上创建定义好的资源。这个过程完成后有一步至关重要获取环境变量。azd会在项目根目录的./.azure/环境名称/下生成一个.env文件。这个文件包含了访问已创建资源的所有关键连接信息。对于我们来说最需要关注的是AZURE_OPENAI_ENDPOINT它的格式类似于https://cog-你的资源名.openai.azure.com/。这个终结点Endpoint是你的函数应用与Azure OpenAI服务通信的地址。3.3 本地调试配置local.settings.json的学问为了让函数在本地运行时能连接到你的Azure OpenAI资源你需要创建一个local.settings.json文件。这个文件相当于本地开发时的“应用程序设置”。项目提供的示例配置有几个关键点{ IsEncrypted: false, Values: { FUNCTIONS_WORKER_RUNTIME: python, AzureWebJobsFeatureFlags: EnableWorkerIndexing, AzureWebJobsStorage: UseDevelopmentStoragetrue, AZURE_OPENAI_ENDPOINT: https://your-deployment.openai.azure.com/, AZURE_OPENAI_CHATGPT_DEPLOYMENT: chat, OPENAI_API_VERSION: 2023-05-15 } }AzureWebJobsStorage设置为UseDevelopmentStoragetrue是使用本地Azurite模拟器来模拟Azure存储用于Functions运行时的一些内部管理如绑定本地调试时非常方便无需真实的存储账户。AZURE_OPENAI_CHATGPT_DEPLOYMENT这里设置为chat。这是一个需要特别注意的地方。这个值必须与你Azure OpenAI资源中实际部署的模型部署名称完全一致而不是模型名如gpt-35-turbo。你需要在Azure门户中进入你的Azure OpenAI资源在“模型部署”部分查看或创建部署并记住它的名字。OPENAI_API_VERSION指定使用的Azure OpenAI API版本。不同版本可能支持的特性略有不同保持与SDK兼容即可。实操心得local.settings.json默认被.gitignore排除在版本控制之外这是为了防止将密钥等敏感信息误提交到代码库。本项目推崇使用Entra ID托管身份进行无密钥认证这比在配置里直接写API密钥更安全。但在本地开发时你的本地身份通过az login登录需要被授权访问该Azure OpenAI资源否则函数调用会失败。4. 核心代码深度解析与扩展实践4.1 函数入口与初始化剖析让我们深入看看function_app.py的核心部分。首先是init()函数它在函数应用启动时运行对于Python v2模型在main函数被首次调用前用于初始化全局资源。import azure.functions as func import logging from langchain_openai import AzureChatOpenAI from langchain_core.prompts import PromptTemplate # 关键全局变量避免每次请求都重新初始化 llm None llm_prompt None def init(): global llm, llm_prompt if llm is not None: return # 从环境变量读取配置 openai_endpoint os.environ[AZURE_OPENAI_ENDPOINT] deployment_name os.environ[AZURE_OPENAI_CHATGPT_DEPLOYMENT] openai_api_version os.environ.get(OPENAI_API_VERSION, 2023-05-15) # 初始化LangChain的Azure OpenAI LLM对象 llm AzureChatOpenAI( azure_endpointopenai_endpoint, deployment_namedeployment_name, openai_api_versionopenai_api_version, temperature0.3, # 控制创造性的参数 max_tokens800, # 可增加限制响应最大长度 # api_key 未显式设置默认尝试使用Entra ID认证 ) # 构建提示词模板 llm_prompt PromptTemplate.from_template( The following is a conversation with an AI assistant. The assistant is helpful, creative, clever, and very friendly.\n\n Human: {human_input}\n AI: )代码解读与技巧全局变量llm和llm_prompt被定义为全局变量并在init()中初始化。这在无服务器环境中是一个重要优化。Azure Functions实例可能会被复用热启动全局初始化可以避免每次函数调用都重新建立连接和加载模板从而降低延迟和成本。认证AzureChatOpenAI初始化时没有传入api_key。在Azure环境中包括本地使用az login后SDK会自动尝试使用DefaultAzureCredential链来获取令牌这包括了托管身份、环境变量、VS Code登录、Azure CLI登录等多种方式实现了无缝的安全认证。模板设计示例模板是一个简单的对话开场。{human_input}是一个占位符将在运行时被替换。你可以根据需要设计复杂得多的模板例如包含系统指令System Message、上下文历史ChatHistory等。4.2 主函数逻辑与链式调用主函数main由HTTP请求触发app func.FunctionApp() app.route(routeask, auth_levelfunc.AuthLevel.ANONYMOUS) app.function_name(nameAskFunction) def main(req: func.HttpRequest) - func.HttpResponse: init() # 确保初始化已完成 logging.info(Python HTTP trigger function processed a request.) try: # 1. 解析请求 req_body req.get_json() user_prompt req_body.get(prompt) if not user_prompt: return func.HttpResponse(Please pass a prompt in the request body, status_code400) # 2. 格式化提示词 formatted_prompt llm_prompt.format(human_inputuser_prompt) # 3. 调用LLM response llm.invoke(formatted_prompt) # 4. 处理并返回响应 # response 是一个AIMessage对象内容在 .content 属性中 answer response.content if hasattr(response, content) else str(response) return func.HttpResponse(json.dumps({response: answer}), mimetypeapplication/json) except ValueError: return func.HttpResponse(Invalid JSON in request body, status_code400) except Exception as e: logging.error(fAn error occurred: {str(e)}) return func.HttpResponse(fInternal server error: {str(e)}, status_code500)流程解析请求解析从HTTP POST的JSON体中提取prompt字段。这里做了简单的校验如果为空则返回400错误。在实际生产中你可能需要更严格的输入验证和清理防止提示词注入攻击。提示词格式化使用之前定义好的llm_prompt模板将用户输入的user_prompt填入{human_input}的位置生成一个准备发送给模型的完整提示词。模型调用llm.invoke(formatted_prompt)是LangChain的标准调用方式。它会将格式化后的提示词发送到Azure OpenAI端点并等待响应。这里的调用是同步的对于Functions的HTTP触发器需要注意函数执行超时时间默认5分钟可配置。响应处理LangChain的invoke方法返回的是一个结构化的消息对象如AIMessage。我们需要从中提取出文本内容.content。最后将回答包装成JSON格式返回给客户端。4.3 超越示例构建更复杂的链示例项目只是一个起点。LangChain的强大之处在于“链”Chain。我们可以轻松地将这个简单的调用升级为更复杂的逻辑。例如创建一个“翻译-总结”链用户输入一段英文长文本先让模型翻译成中文再对中文内容进行总结。from langchain.chains import LLMChain, SequentialChain from langchain_core.prompts import PromptTemplate # 初始化LLM (同上) # ... # 定义第一个链翻译 translation_prompt PromptTemplate.from_template( 请将以下英文文本翻译成流畅、地道的中文\n\n{english_text} ) translation_chain LLMChain(llmllm, prompttranslation_prompt, output_keychinese_text) # 定义第二个链总结 summarization_prompt PromptTemplate.from_template( 请用一句话总结以下中文文本的核心内容\n\n{chinese_text} ) summarization_chain LLMChain(llmllm, promptsummarization_prompt, output_keysummary) # 组合成顺序链 overall_chain SequentialChain( chains[translation_chain, summarization_chain], input_variables[english_text], output_variables[chinese_text, summary], verboseTrue # 调试时查看中间步骤 ) # 在main函数中调用组合链 def main(req: func.HttpRequest): # ... 解析请求 english_text req_body.get(text) result overall_chain.invoke({english_text: english_text}) # result 是一个字典包含 chinese_text 和 summary return func.HttpResponse(json.dumps(result), mimetypeapplication/json)通过这种方式你可以利用Azure Functions的HTTP接口对外提供复杂的、多步骤的AI处理流水线服务。5. 测试、部署与运维实操指南5.1 本地测试的多种姿势项目提供了几种测试方法适应不同开发习惯。使用Functions CLI在项目根目录安装依赖pip install -r requirements.txt。启动本地Functions主机func start。你会看到输出中显示http://localhost:7071以及列出的函数端点api/ask。使用curl命令测试curl -X POST http://localhost:7071/api/ask \ -H Content-Type: application/json \ -d {\prompt\: \用通俗的语言解释一下什么是无服务器计算\}使用项目自带的test.http文件需安装VS Code的REST Client插件或Postman可以更直观地发送请求和查看响应。使用Visual Studio Code用VS Code打开项目文件夹它会识别为Azure Functions项目。按F5启动调试。VS Code会自动处理虚拟环境、依赖安装和函数主机启动。在调试控制台看到函数启动后同样可以使用上述HTTP工具进行测试。VS Code的调试功能允许你设置断点单步跟踪init()和main()函数的执行对于调试LangChain逻辑和API调用异常非常有用。5.2 部署到Azure一键式与精细化一键式部署推荐用于快速原型 使用Azure Developer CLI是最简单的方式。在项目根目录下确保已登录Azure (az login)然后运行azd up这个命令会依次执行azd provision再次确认或创建基础设施资源。azd deploy将你的代码打包并部署到上一步创建的函数应用中。 部署成功后azd会输出函数的HTTPS端点URL你可以像在本地一样用这个URL进行测试。精细化部署与配置 对于生产环境你可能需要更多控制创建函数应用可以在Azure门户手动创建或使用Azure CLI、Bicep/ARM模板。配置应用程序设置在Azure门户中进入你的函数应用找到“配置”-“应用程序设置”。你需要添加所有在local.settings.json中定义的Values特别是AZURE_OPENAI_ENDPOINT和AZURE_OPENAI_CHATGPT_DEPLOYMENT。切记不要将本地文件直接上传。配置身份认证关键步骤为了让函数应用以托管身份运行能访问Azure OpenAI你需要为函数应用的系统分配托管身份System Assigned Managed Identity在Azure OpenAI资源上授予“认知服务 OpenAI 用户”角色。在Azure门户中进入你的Azure OpenAI资源 - “访问控制(IAM)” - “添加角色分配”。选择角色“认知服务 OpenAI 用户”。在“成员”中选择“托管身份”点击“选择成员”然后找到你的函数应用并选择它。部署代码可以使用VS Code的Azure Functions扩展、GitHub Actions、Azure DevOps Pipelines或者使用Functions Core Toolsfunc azure functionapp publish 你的函数应用名称。5.3 生产环境考量与优化将这样一个AI服务投入生产还需要考虑以下几点超时与并发Azure Functions有不同的托管计划消耗计划、高级计划、专用计划。消耗计划默认超时时间为5分钟对于极复杂的链可能不够。高级计划和专用计划支持更长的超时时间和更稳定的冷启动性能。同时要监控函数的并发执行实例数确保能处理预期的负载。错误处理与重试示例中的异常处理比较基础。在生产中需要对Azure OpenAI API可能返回的速率限制错误429、服务不可用错误503等实现重试逻辑可以使用指数退避。LangChain本身也为很多LLM提供者内置了重试机制。日志与监控利用Azure Functions与Application Insights的集成记录详细的日志。除了记录错误还可以记录每个请求的提示词、响应时间、Token使用量等这对于成本分析和性能优化至关重要。API管理如果要将此API开放给多个客户端或前端应用建议在前面加一层Azure API管理APIM。APIM可以提供速率限制、身份验证、请求转换、缓存等能力并生成API文档。成本控制Azure OpenAI按Token收费。在代码中可以设置max_tokens参数来限制单次响应的长度。同时监控Application Insights中的相关指标设置预算警报。6. 常见问题排查与调试技巧实录在实际操作中你可能会遇到一些典型问题。这里记录了几个我踩过的坑和解决方法。6.1 认证失败401或AuthenticationError这是最常见的问题表现为函数调用时抛出认证错误。本地开发检查登录状态运行az account show确认你已用az login登录且当前订阅有权限访问目标Azure OpenAI资源。检查环境变量确认local.settings.json中的AZURE_OPENAI_ENDPOINT完全正确没有多余的斜杠或错误。检查部署名称确认AZURE_OPENAI_CHATGPT_DEPLOYMENT的值与你Azure门户中“模型部署”下的名称一字不差区分大小写。尝试显式提供凭据临时调试在代码中暂时使用DefaultAzureCredential并打开详细日志看它尝试了哪种认证方式失败。from azure.identity import DefaultAzureCredential, AzureCliCredential credential DefaultAzureCredential(logging_enableTrue) # 或者在明确知道用CLI时 # credential AzureCliCredential() llm AzureChatOpenAI( azure_endpointendpoint, deployment_namedeployment, azure_ad_token_providerlambda: credential.get_token(https://cognitiveservices.azure.com/.default).token, api_versionapi_version )Azure部署环境确认托管身份已开启在函数应用的“标识”设置中确保“系统分配”的状态为“开启”。确认角色分配成功在Azure OpenAI资源的IAM中检查是否已为函数应用的托管身份成功分配了“认知服务 OpenAI 用户”角色。角色分配可能需要几分钟才能生效。检查应用程序设置在函数应用的配置中仔细核对AZURE_OPENAI_ENDPOINT和AZURE_OPENAI_CHATGPT_DEPLOYMENT的值确保没有拼写错误并且与本地测试时使用的资源是同一个。6.2 模型部署未找到404或DeploymentNotFoundError错误信息可能提示The API deployment for this resource does not exist。根本原因AZURE_OPENAI_CHATGPT_DEPLOYMENT设置的值在指定的AZURE_OPENAI_ENDPOINT资源下不存在。解决步骤登录Azure门户导航到你的Azure OpenAI资源。在左侧菜单中选择“模型部署”。查看列表中的“部署名称”。你必须使用这个列表里的名字而不是模型类型如“gpt-35-turbo”。如果你还没有部署点击“创建新部署”来创建一个。将正确的部署名称更新到你的环境变量或应用程序设置中。6.3 函数启动失败或导入错误在本地运行func start或部署后函数无法启动日志显示ModuleNotFoundError。原因依赖包没有正确安装或打包。解决方案本地确保在激活的虚拟环境中运行pip install -r requirements.txt。检查requirements.txt文件是否存在且包含langchain-openai,azure-functions等必要包。远程部署Azure Functions在部署时会自动根据requirements.txt安装依赖。如果失败检查部署日志。一个可靠的实践是使用“在SCM中生成部署依赖项”功能在函数应用“高级工具”Kudu站点中或考虑将依赖打包到Docker容器中进行部署。6.4 响应慢或超时函数调用耗时很长甚至触发超时默认5分钟。分析延迟可能来自1) 冷启动函数实例初始化2) LangChain/Azure OpenAI SDK的初始化3) 模型本身生成响应的时间。优化建议利用全局变量正如示例代码所做将llm和llm_prompt对象在全局范围初始化并复用可以极大减少每次调用的开销。调整函数配置对于生产环境考虑使用Azure Functions高级托管计划它提供更快的实例启动速度和预热的实例。优化提示词和参数检查你的提示词是否过于复杂冗长。调整LLM调用参数如max_tokens不要设置得过大temperature保持较低值如0.3通常响应更快更稳定。启用应用程序洞察详细监控每个请求的端到端耗时定位瓶颈是在网络、SDK还是模型推理。6.5 如何处理流式响应当前示例是等待完整响应生成后再返回。对于长文本生成用户可能需要流式输出以获得更快的感知速度。方案Azure OpenAI API和LangChain都支持流式响应。在Azure Functions中你可以使用StreamingHttpResponse如果Functions运行时支持或利用服务器发送事件Server-Sent Events, SSE来实现。LangChain实现将llm.invoke()改为llm.stream()它会返回一个异步生成器。# 注意这需要函数支持异步或处理生成器 async def main(req: func.HttpRequest): # ... 初始化 response_stream llm.stream(formatted_prompt) # 构建一个生成器来逐块返回数据 async def generate(): async for chunk in response_stream: if hasattr(chunk, content): yield fdata: {json.dumps({text: chunk.content})}\n\n return func.HttpResponse(generate(), mimetypetext/event-stream)这需要更高级的函数配置和对前端处理SSE的支持。这个项目模板就像一颗种子为你展示了在Azure无服务器架构上快速构建AI应用接口的最小可行路径。从简单的提示词补全到复杂的多步推理链其扩展的边界取决于你对LangChain的掌握和业务需求的理解。最关键的是它提供了一个符合生产实践的安全、可扩展的起点让你能避开基础设施的琐碎专注于创造AI本身的价值。