AI端点如何改变传统API流程
原文:https://dev.to/gramli/how-ai-endpoints-change-the-traditional-api-flow-3773
作为后端开发者,我构建过数百个端点,因此典型的端点流程已深深烙印在我对Web应用的思考方式中。但当我开始构建AI驱动的端点时,我注意到一个有趣的转变。
起初,AI端点看起来像是简单的代理端点,只需配置模型连接:
API接收请求
↓
向模型发送提示词
↓
接收响应
↓
返回给客户端
这个模式运行良好,直到我发现直接从客户端传递提示词并非良策。端点可能被滥用于完全不同的目的,导致他人消耗我的AI使用额度。
随后我意识到输入也需要限制。为特定任务发送大量上下文不仅成本更高,还可能产生意外结果。
因此当我深入审视时,特别是在需要可靠的结构化输出和可预测的应用行为时,我很快意识到事情没那么简单。验证不再仅仅是执行前的防护,它已变成后处理步骤。AI模型具有概率性。即使输入相同,它们可能返回不同输出、遗漏必要信息、误解指令,或返回技术上有效但逻辑错误的结果。而且由于每个token都有成本,我不能简单地重试请求并期待更好结果。
正是这时我开始质疑:AI端点是否应该采用与传统Web API端点相同的方式设计?
目录
- 传统Web API端点流程
- AI驱动的Web API端点流程
- 这种差异带来的改变
- 不可预测的延迟
- 重试逻辑
- 幂等性与副作用
- 测试AI端点
- 输出契约
- 可观测性与成本
- 总结
传统Web API端点流程
传统Web API端点通常遵循类似流程:
验证请求
↓
执行业务逻辑
↓
返回表示
第一阶段是请求验证。我们根据属性约束、API契约、授权规则和应用特定业务规则验证传入数据。
第二阶段是执行。应用处理数据、执行I/O操作或执行业务逻辑。
最后阶段是返回结果的表示。传统端点执行的代码在已知应用状态下通常是确定性的。当相同代码在相同状态下运行时,我们通常得到相同结果。
基本就是这样。
传统Web API端点的一般流程相对简单,尽管单个步骤可能非常复杂。但AI模型的工作方式并不完全相同。它们不会简单地执行预定义的指令序列并始终产生相同结果。
AI驱动的Web API端点流程
AI驱动端点的内部流程通常更复杂:
验证请求
↓
准备提示词、工具和上下文
↓
生成概率性输出
↓
验证模式、含义和安全性
↓
重试、修复、拒绝或回退
↓
返回表示
我们仍然从验证传入请求开始。标准属性约束和业务规则仍然重要,但AI端点可能还需要额外控制。应用可能需要限制请求主题、限制输入大小、对可疑指令施加控制、隔离不可信的检索内容,或决定模型允许调用哪些工具。
下一步是准备模型调用的提示词和设置。应用准备指令、对话历史、检索上下文、工具定义和生成设置。
然后模型产生输出。但与常规业务逻辑的结果不同,我们不能仅因模型调用成功就自动假设输出正确。来自模型提供商的成功HTTP响应只告诉我们模型生成了某些内容,并不告诉我们结果是否完整、安全、有依据甚至有用。
因此验证在生成后再次出现。例如,我们可能以类似验证传统外部服务响应的方式验证JSON模式。但即使模式正确,结果在业务领域仍可能逻辑错误。模型也可能遗漏那些技术上非模式必需但基于提供上下文应存在的属性。
当输出未通过这些检查时,应用必须决定下一步操作。它可能尝试修复响应、要求模型重新生成、使用回退模型、返回受控错误或将请求提交人工审核。但每个决策都有其成本。重试消耗更多token,而人工干预既耗时又费钱。
所有这些将端点转变为编排管道,而非围绕模型调用的简单代理。
这种差异带来的改变
使用传统Web API时,应用明确定义结果的创建方式。这意味着执行路径由代码控制,我们知晓预期结果。
使用AI驱动的Web API时,应用将部分结果创建委托给概率系统。这意味着我们不再完全控制结果的生成方式。为达到预期结果,我们需要在流程中添加额外步骤,如后处理和输出验证。
我们可以这样简化:
- 传统Web API:后端执行规则。
- AI驱动的Web API:后端编排并评估结果。
但这种差异影响的远不止验证。它还改变了我们对延迟、重试、幂等性、测试、可观测性、成本和输出契约的思考方式。
肯定还有更多差异,但让我们看看我在实践中发现的这些方面。
不可预测的延迟
传统端点的延迟通常由业务逻辑、I/O操作、数据处理等决定。这些操作并非总是快速或完全可预测,但我们通常可以单独测量它们,并通过改进代码、优化数据库查询或添加缓存来优化最慢的部分。
AI端点引入了另一层变异性。
生成时间可能取决于所选模型、提示词和上下文大小、输出长度、提供商负载等因素。一个请求可能在单次模型调用后完成,而另一个可能需要多次工具调用、验证尝试或重新生成步骤。
因此延迟不再仅由我们显式执行的操作决定,还可能取决于模型生成过程中的决策。
这使得超时、取消、流式传输、异步处理和延迟预算变得更加重要。
改善AI驱动端点延迟的最简单方法之一是改进提示词。我们可以使其更具体、减少不必要的上下文并限制预期输出。
另一个例子是避免模型返回完整对象。
例如,我们可能提供如下数据:
{
"data": [
{
"id": 1,
"name": "john doe",
"address": "Mordor",
"category": "Nazgûl"
},
{
"id": 2,
"name": "joe doe",
"address": "Gondor",
"category": "soldier"
}
]
}
然后我们可以指示模型仅返回选中的ID,而非重复完整对象。
后端可以在生成后将这些ID映射回原始数据。这减少了输出token数量,可能同时改善延迟和成本。
核心觀點在於我們處理延遲優化的方式不同。傳統端點通常優化程式碼、資料庫存取或快取。而AI端點還需要優化提示詞、上下文大小、模型選擇和生成輸出。
重試邏輯
在傳統Web API中,我們通常在特定條件下重試I/O操作。例如,當依賴服務暫時不可用、連線中斷或外部服務返回暫時性狀態碼時,我們可能會重試。這些重試通常基於技術性失敗。
AI端點引入了另一種重試類型。請求可能在傳輸層成功完成,但生成的輸出仍然無法使用。例如,它可能缺少必要欄位、違反預期結構、與提供的上下文矛盾或違反業務規則。
每次重試都會增加延遲並消耗額外的token,這意味著額外的成本。下一次嘗試可能返回不同但仍然不正確的回應。
因此,AI重試不應被視為普通的網路重試。它們需要明確的限制,我們還應考慮其他約束條件,例如token和預算限制、失敗原因、再次嘗試是否有幫助,以及是否有備用模型或確定性替代方案可用。
讓我們看看這個C#重試管道,我將它與透過Ollama暴露的本地模型一起使用:
private static class RetryPipeline<T>
{
public static readonly ResiliencePipeline<Result<T>> Instance = new ResiliencePipelineBuilder<Result<T>>()
.AddRetry(new RetryStrategyOptions<Result<T>>
{
MaxRetryAttempts = 2,
Delay = TimeSpan.FromSeconds(2),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
ShouldHandle = new PredicateBuilder<Result<T>>()
.Handle<HttpRequestException>()
.Handle<JsonException>()
.Handle<TaskCanceledException>(ex =>
!ex.CancellationToken.IsCancellationRequested)
.HandleResult(r => r.IsFailed)
})
.Build();
}
該管道在收到HttpRequestException、JsonException或TaskCanceledException時會重試。它允許最多兩次重試,並使用指數退避策略,初始延遲為兩秒。
到目前為止,這看起來像是一個傳統API端點的典型彈性管道。
重要的區別在於這個條件:
.HandleResult(r => r.IsFailed)
當操作沒有拋出異常但返回失敗結果時,它也會重試。
在該管道執行的動作內部,我會驗證AI輸出。如果回應在技術上有效但在邏輯上錯誤,我會返回失敗結果並讓管道再次嘗試。
這種方法在使用付費模型提供者時可能不理想,因為每次重試都會消耗額外的token並增加成本。但對於本地或免費模型,少量且嚴格限制的重試次數可能就足夠了。
有時正確的決定是不重試。拒絕輸出、返回受控錯誤或向用戶請求更多資訊可能是更好的選擇。
冪等性與副作用
冪等性意味著多次執行相同操作的效果與執行一次相同。
一個簡單的例子是傳統的GET端點:
GET /api/orders/123
我們可以多次呼叫此端點而不改變訂單。如果底層資料發生變化,返回的表示可能會改變,但請求本身不會產生額外的副作用。
對於AI端點,當模型可以呼叫工具時,冪等性變得更加複雜。
多次發送相同的提示詞可能會產生不同的輸出或略微不同的措辭。對於唯讀端點,這可能是可以接受的,因為伺服器狀態沒有改變。
但當端點可以執行操作時,情況就變得更加危險。例如,模型可以透過工具建立訂單、發送電子郵件或更新資料。如果輸出驗證後來失敗並且整個操作被重試,模型可能會再次呼叫相同的工具並建立第二個訂單。
這就是為什麼由AI端點觸發的操作應該使用保護措施,例如冪等性金鑰、持久化的操作結果和唯一約束。使用相同操作ID重複執行相同操作應返回原始結果,而不是再次執行副作用。
測試AI端點
當應用程式狀態和依賴項可控時,測試傳統端點相對直接。
我們準備資料、執行端點並斷言預期結果,例如狀態碼、預期的回應值、更新的應用程式狀態或外部依賴項是否被呼叫。
對於AI端點,精確輸出斷言通常很脆弱。相同的有效答案可以用許多不同的方式表達。模型更新也可能在保持相同含義的同時改變措辭。但僅斷言回應狀態或檢查欄位不為null又過於薄弱。
與其總是比較精確的回應,我們可以驗證結果的必要屬性。根據用例,我們可以斷言值在允許範圍內、不包含禁止內容、工具呼叫遵守允許清單以及回應遵循預期的業務規則。
例如,使用Assert.Equal斷言完整回應會很脆弱:
var result = await endpoint.GeneratePlanAsync(request);
// 脆弱的斷言
Assert.Equal("Family Day in Brno", result.Title);
Assert.Equal(
"Visit the science centre and have lunch nearby.",
result.Description);
Assert.Equal(1_500, result.TotalCost);
模型可能返回不同的標題或描述,但仍然產生完全有效的計劃。
與其檢查精確回應,我們可以使用InRange、Contains和DoesNotContain等方法斷言對應用程式重要的屬性:
var result = await endpoint.GeneratePlanAsync(request);
Assert.NotNull(result);
Assert.NotEmpty(result.Activities);
// 斷言
Assert.InRange(result.TotalCost, 0, request.Budget);
Assert.All(
result.Activities,
activity =>
{
Assert.Contains(
activity.Type,
request.AllowedActivityTypes);
Assert.InRange(
activity.TravelTimeMinutes,
0,
request.MaximumTravelMinutes);
Assert.False(
string.IsNullOrWhiteSpace(activity.Name));
});
Assert.DoesNotContain(result.Activities, activity => activity.IsClosed);
Assert.All(result.Activities, activity => Assert.NotEmpty(activity.SourceIds));
標題、描述或活動順序可能會改變,但生成的計劃必須仍然遵守預算、旅行時間限制、允許的活動類型和提供的來源資料。
端點的大部分功能仍然可以確定性地測試。結構驗證、授權、工具權限、解析、回退邏輯和副作用處理仍應像普通應用程式程式碼一樣進行測試,但我們應該改變斷言生成結果的方式。
輸出契約
當傳統端點呼叫外部服務時,我們通常依賴JSON解析器和明確定義的契約。當服務返回無效JSON或不符合預期結構的回應時,反序列化會失敗,應用程式會處理錯誤。
AI輸出產生了一個更微妙的問題。
模型可能返回完全有效的JSON,符合預期結構,但在邏輯上仍然是錯誤的。
例如:
{
"approved": true,
"reason": "客户满足所有必要条件。",
"failedConditions": [
{
"name": "age",
"value": 13,
"description": "客户未达到所需年龄。"
}
]
}
该响应是有效的JSON,也可能符合预期的schema。但它自相矛盾:尽管某个必要条件未通过,客户仍被标记为已批准。
因此,schema验证是必要的,但还不够。
AI输出可能需要额外的验证层级,例如业务规则验证、逻辑一致性检查和安全性验证。
端点必须能够区分可解析的响应和实际可信的响应。
可观测性与成本
传统端点监控通常关注请求数、延迟、错误、依赖调用和资源使用等指标。
AI端点也需要这些指标,但还引入了模型特有的指标:
- 输入和输出token使用量
- 模型及模型版本
- 生成尝试次数
- 工具调用
- 验证失败次数
- 修复成功率
- 预估成本
- 输出质量或评估分数
缺乏这些指标,将难以理解端点为何变慢、更昂贵或可靠性下降。
提供商可能更改别名背后的模型。提示词可能随时间增长,检索到的上下文可能变大,重试频率可能增加。端点可能仍返回200 OK,但其成本增加,输出质量逐渐下降。
总结
传统端点执行由代码定义和控制的业务逻辑。
AI端点则不同。它们协调概率生成过程,验证结果,并决定结果是否足够可靠以返回。
这改变了我们思考和设计使用AI模型的端点的方式。
AI端点不应仅仅是模型调用的薄代理。模型生成输出,但后端仍决定其是否应成为最终响应。
这些是我在创建、调试和维护AI端点时发现的一些最大差异。当然还有更多,但仅这几个例子就足以说明,在端点后添加AI模型会显著改变其流程。