在现有的 .NET 服务及 API 基础上构建 AI 智能体
C#团队可以通过函数调用,将现有的.NET服务作为受管控的工具提供给AI智能体使用,并确保具备所需的上下文、安全性和可观测性机制。
来源: AI Agents Are Changing C# Development: 10 Things Every .NET Developer Should Know
大多数 .NET 团队已经掌握了 AI 智能体所需的核心技术:类型化服务、HTTP API、身份验证、数据库以及后台任务处理。新的突破在于出现了一种语言模型,它能够根据用户用通俗语言描述的目标,决定以何种顺序调用这些功能来实现该目标。本指南将介绍 C# 开发者如何在不失去对应用程序实际运行逻辑控制权的情况下,将其与智能体整合在一起。
从回答问题到完成任务
传统的聊天机器人仅进行单次交互:用户提出问题,模型以文本形式回复。而智能体则是围绕目标来工作——它先分析目标的要求,调用相关工具获取信息或执行操作,查看结果之后才给出最终答案。
假设有一位用户想知道今天收到了多少订单。聊天机器人只能猜测或说明如何获取这些信息,而拥有合适工具的客服人员则可以直接查询订单数据、筛选今日的记录、分析相关数字、撰写简短总结,并以报告的形式呈现结果。两者的实际区别很简单:聊天机器人负责提供答案,而客服人员则负责执行具体工作。
工具让模型变得实用
仅凭语言模型本身,它根本不知道你的应用程序是如何运作的,也无法读取客户数据表或处理订单。工具起到了桥梁的作用——它们是模型可以被允许调用的特定操作。对于商业后台系统而言,典型的工具组合可能如下所示:
GetCustomer()
CreateOrder()
SearchProducts()
GetWeather()
SendEmail()
QueryDatabase()
模型决定何时需要这些功能中的某一种;而你的代码则负责决定其运行方式。在 C# 中,工具并不一定非得是什么特别复杂的存在,它可以是你现有服务中的一个普通异步方法,比如通过注入的服务按 ID 加载客户信息的那种方法:
public async Task<Customer> GetCustomer(int id)
{
return await customerService.GetByIdAsync(id);
}
一旦该方法被注册为工具,代理就可以使用这一功能,而其实现仍保留在你的常规服务层中,与其他代码库一样受到依赖注入、测试和日志记录机制的覆盖。这就是日常应用程序开发与人工智能开发相互交汇的地方。
函数调用的实际工作原理
函数调用是工具背后的机制。模型本身不会执行任何操作;它只会发出带有特定参数的结构化请求来调用函数,然后由你的应用程序来完成实际调用。
假设用户询问订单12345的状态。在内部,模型会判定自己缺乏相关信息:
I need order information.
于是它会请求主机应用程序调用一个以订单号为参数的函数:
GetOrderStatus(12345)
你的代码会执行该请求,将结果返回给模型,模型再将原始数据转换为可读的回复。对于C#开发者来说这应该很熟悉:你正在公开应用程序的一小部分功能,就像公共API一样,只不过调用方是模型。清晰的名称、精确的参数类型以及完善的描述能大大提高正确调用的概率。
重用现有的API
采用智能体并不需要重写后端代码。如果你的应用程序已经提供了接口,这些接口就可以成为智能体的工具。某个服务可能已经提供了客户查询功能:
GET /api/customers/{id}
除了用于读取订单、创建订单以及列出产品的接口外(下方的代码将这三条路由合并到同一行中;实际应视为独立的接口):
GET /api/orders/{id}POST /api/ordersGET /api/products
有了这些接口之后,诸如找出本月下单最多的客户之类的请求,就变成了系统可以自行处理的流程:获取客户数据、检索订单信息、对比相关数值并确定获胜者。该系统实际上只是在你已构建并保障安全的功能之上的另一个客户端而已。
背景:为模型提供它所需的内容,而非你拥有的所有东西
想象一下,与负责处理你项目的智能体进行半小时的交流,内容涵盖应用程序、其数据库结构、团队编码规范、业务规则、未解决的漏洞以及之前讨论中做出的决策。为了保持实用性,该智能体必须记录下所有这些信息的相关部分。正因如此,在智能体设计中才会不断出现对话历史、上下文管理、记忆功能、信息检索、嵌入技术以及向量数据库等概念。
有一个容易被忽视的陷阱:更多的上下文并不一定意味着更好。每多一个标记都会增加成本和延迟,而无关信息则会稀释有效信号,从而导致答案的可靠性下降。优秀的智能体设计应能在需要时提供恰当的上下文,例如仅检索与当前步骤相关的文档,而非在每次请求中都插入整个知识库。如需更深入地了解这些记忆机制的差异,请参阅我们关于AI记忆、嵌入技术及RAG的解读文章。
让智能体安全地查询数据
在数据访问方面,智能体对.NET应用开发团队而言具有特别大的吸引力。业务用户可能会询问上个月哪些产品的销量下降最为严重。与其为每个此类问题都创建专门的报表,不如使用基于智能体的系统来理解问题、确定相关数据、执行查询、分析结果并解释发现的内容。
不过这里有一个不可忽视的警告:绝不能让模型无限制地访问数据库。允许其生成和运行任意的SQL语句可能会导致破坏性操作、数据在不同租户之间泄露,以及锁定表的查询行为。相反,应通过经过验证的参数及相应的权限检查,仅暴露那些为特定目的设计的有限操作:
GetSalesReport()
GetProductSales()
GetCustomerOrders()
这些方法能够实现行级安全控制、限制结果数量,并使用只读连接,从而以一定的灵活性换取更高的可预测性。
智能体不能替代架构设计
一个常见的误解是,只要加入了模型就可以忽视基本原则。事实恰恰相反。一旦由非确定性组件来决定调用什么功能,依赖注入、SOLID设计原则、日志记录、测试、身份认证、权限控制、错误处理、可观测性以及清晰的层次结构就显得更为重要。
一个有用的思维模型是角色分离:智能体可以提出某个操作建议,但是否允许该操作由应用程序来决定。简而言之,AI负责决策,应用程序负责管控。权限检查应放在工具实现及其背后的服务中,绝不能出现在提示语里,因为提示语只是对模型的建议,而非必须遵守的规则。
每增加一个工具,安全风险就会倍增
你授予的每一个工具都会增加出错的可能性。智能体或许能够执行以下操作:
Read customer data
Send emails
Create orders
Delete records
Call external APIs
接下来有三个问题。当模型出现错误,比如删除了错误的记录时会发生什么?当恶意用户试图诱使智能体滥用工具时又会怎样?而如果智能体读取的外部内容,如邮件或网页,其中包含隐藏指令又该如何处理?最后这种情况通常被称为间接提示注入,这是一种真实存在且已有大量相关记录的攻击方式,而非虚构的思考实验。
防御措施大多是你已经了解的、需要刻意应用的手段:
- 对每一次工具调用进行身份验证和授权,范围应针对最终用户而非智能体。
- 对模型提供的所有参数进行输入验证。
对于退款、删除数据或向客户发送消息等高影响操作,通过人工审批来确认操作往往是成本最低的保障措施。
非线性请求流程的可观测性
传统的请求处理流程是线性的,因此很容易追踪:
Request → Service → Database → Response
而实际中的客服流程则可能在模型、多个工具及不同后端之间反复切换,才能最终生成响应:
User
↓
AI Model
↓
Tool
↓
API
↓
Database
↓
AI Model
↓
Another Tool
↓
Final Response
那种结构要调试起来困难得多。一旦出现问题,就需要弄清楚使用了哪些工具、模型为何选择这些工具、每次调用耗时多久、什么地方出了故障、本次运行需要调用模型多少次,以及整个操作的代价是多少。应将每次运行视为一条跟踪记录,其中包含模型调用和工具调用的时间片段,并在时间数据旁记录参数及令牌数量;否则几乎不可能重现错误的结论。
为何C#开发者更具优势
熟悉C#和.NET的开发者并非从零开始。接口与类、HTTP API、依赖注入容器、异步代码、数据库、身份验证机制、托管后台服务以及云部署,正是构建代理系统所依赖的基础。剩下的只是一小部分新概念,它们大致按以下顺序相互关联:
LLMs
↓
Prompting
↓
Function Calling
↓
Tools
↓
RAG
↓
Agents
↓
Multi-Agent Systems
无需一次性全部吸收;从小处着手。
参考架构
最简设计将代理置于中心位置,若干工具分别连接到您的服务、数据库以及外部搜索API,最终结果通过代理反馈给用户:
User
↓
AI Agent
↓
┌───────┼────────┐
↓ ↓ ↓
API Tool DB Tool Search Tool
↓ ↓ ↓
.NET Database External API
└───────┼────────┘
↓
Agent Result
↓
User
这种布局的关键在于代理从不直接接触您的系统。所有数据流都会经过.NET应用程序所拥有的工具,因此访问控制、验证和日志记录都集中在您已经熟悉的管理范围内。
实用的学习路径
对于转行从事AI工作的C#开发者而言,合理的学习顺序如下:
- 了解大语言模型。学习大型语言模型的运行机制、优势所在以及缺陷,比如给出看似有依据但实际上错误的答案。
客户支持代理是一个很好的首个实际项目,因为它能涉及各个层面。工作人员可以查询客户信息、检查订单状态、查看配送情况、创建工单并予以回复:
Search customer
↓
Check order
↓
Check delivery status
↓
Create support ticket
↓
Respond to customer
每个步骤都对应一种特定的工具,而那些会产生副作用的步骤则是实施审批规则的理想对象。
关键要点
- 代理实际上是一个软件系统:由模型、工具、数据、应用逻辑、安全机制及监控功能共同构成,而非这些组件的替代品。
- 应将各项功能以经过验证的专用工具形式呈现,绝不能允许直接访问原始数据库或使用无限制的API。
- 应在代码中实现授权机制,由应用程序来控制代理可以执行的操作。
- 应为每一步骤精心设定上下文范围,而非尽可能扩大其范围。
- 需对每次运行进行端到端的追踪,以便明确故障原因及相关成本。