阅读视图

发现新文章,点击刷新页面。

小试自定义GPT

最近不是在折腾LLM嘛,于是就试了两条路子:用openai的api,以及直接在openai的界面里面创建GPT。

前者没啥特别的,chatgpt的api做的很成熟了,from openai import OpenAI 之后直接在python里面调用几个现成的函数就好了。可选的参数其实也不多,主要就是prompt写的好一点就行。我的要求也不高,试了试基本满足。此外我还用到了微软 azure api,也很方便,两者一结合基本一个app就搓出来了,只是暂时还只能在命令行运行,没写前端ui罢了。

后者就麻烦了。我想着自己写前端ui还挺麻烦的,就想偷个懒直接在GPT里面弄弄看看行不。结果呢,现在这个版本实在是太挫了,只支持最最基本的action,虽然可以调用其他api,但还没研究出来怎么实现用户上传的文件扔到action api call里面。搜了搜他们的论坛也没啥结果,然后心累就到此为止了。

最后贴一下如何在openai 的GPT里面调用azure api。主要是api key那里实在是反用户直觉,我找了好久……一定要选 custom 然后把自定义的名字设为 Ocp-Apim-Subscription-Key 才可以。贴个图。

自定义 action -> authentication -> custom header name

当然azure api的文档做的也很差就是了,经常搜出来的是过时的文档,试一试都是404错误。哎,时间都花在这些琐碎的调试bug上了。

最后的结论是,在现在这个阶段,openai GPT的多模态做的还是太封闭,只适用于比较基础的交互需求,得等到后面允许自定义编程更丰富一些才可以。想做的稍稍复杂一点,写ui是逃不掉的了。web版还可以写个python+js凑和一下(flask这么轻量级的web开发框架真的是效率提升利器),app版xcode看了半天发现也是一等一的复杂……说好的ai改变程序开发呢?叹口气……

Web API 的开发工具和平台

上一篇文章《Web API 简介》的落脚点是 Web API 的体验。

Web API 作为许多软件的第一道门面,提升其体验的努力从来没有停止过。今天,围绕 Web API 的开发体验和使用体验,已经成长出一个庞大的软件生态。本文以常用的 Web API 开发工具和平台为切入点,介绍当前 Web API 的开发者基础设施。

终端工具

最初的 Web API 开发者显然是黑客,而黑客们的开发工具大多是终端工具,也就是不依靠图形化界面,而是以纯文本来交互的工具。

GNU wget

早期广外流传的 Web API 工具非 GNU wget 莫属。今天,你仍然可以在许多软件的官方网站上看到形如 wget ... 的下载安装指南。

GH Archive 网站提供了 wget 的下载指南

通过 man wget 可以查看其使用手册,共约 2000 行。wget 作为初代 Web API 工具,提供的是基础的下载网络资源的功能。随着网络技术的迭代,它会引入一系列新的参数来支持新的功能,但是整体还是偏向于简单,并且新参数的引入很有时代特色:后来的工具,往往能对早先的技术探索作扬弃和合并,但是一路跟进技术发展的工具,尤其是大众工具,常常因为需要保持向后兼容而不能移除跟进早期探索的不完美实现。

wget 的手册

wget 支持的功能可以这么分类:

  1. 最基本的从一个指定的 URL 下载内容。
  2. 定义下载内容存放到具体路径的系列参数。
  3. DNS 和代理解析的支持。
  4. 下载的重试和超时参数。
  5. 指定 HTTP 的各种具体参数,包括 Cookie 和 Header 等等。
  6. HTTPS (SSL/TLS) 相关的支持。

这也是后续所有 Web API 工具和平台的基本功能集必须包含的能力。

curl

如果说 wget 有一个后继者,那一定就是 curl 了。

许多软件提供基于 curl 的下载命令

wget 只支持 HTTP(S) 和 FTP 两种协议,而 curl 作为 Web API 开发的瑞士军刀,支持的网络通信协议就非常多了:

curl is a tool for transferring data from or to a server. It supports these protocols: DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. The command is designed to work without user interaction.

curl 的参考手册有 6000 余行,是 wget 的三倍。这都显示出 curl 的特点:大而全的功能。curl 对常见操作都做了专门的易用性优化,例如:

  1. 不同的 HTTP method 通过 -X 指定,例如常用的 -X POST 等。
  2. 传入数据通过 -d 指定,并支持 -d @filename 从文件获取传入数据的方式。
  3. HTTP header 通过 -H 指定,比如 GitHub 请求时常用的鉴权信息就是以 -H "Authorization: ..." 的方式传入的,同样还有 -H "Accept: application/json" 指定数据编码信息。

把短参数交给常见操作的命名,可以看出 curl 是对开发者体验下过一番功夫的。

此外,curl 兼顾了终端用户和脚本的需求,即同时提供正交的参数设计和面向终端用户优化的聚合参数。

  • -X-H 这样的通用参数,适合脚本批量生成结构化的 curl 命令。
  • -e-A 这样的聚合参数,适合用户用更短的字符表达常用的操作。

由于 curl 设计当中的正交性和对多样功能的支持,Postman 提供了把请求导出为 curl 的功能。ReadMe 提供的页面内,从命令行提交 Web API 请求的也是一个 curl 命令。

jq

jq 是一个非常年轻的命令行工具,专门用来分析 JSON 数据。从 2012 年开始开发,十年间已经获得 2.5 万个 star 的成绩。由于其广泛使用,甚至在 GitHub Actions 的官方 Runner 中都预装了这个软件。

jq star history

作为一个专门的 JSON 数据分析工具,它在 Web API 的开发工具中占据一席之地的原因,是目前几乎所有返回复杂结果的 Web API 都采用 JSON 的格式编码数据。

jq 最基本的使用方式是 curl ... | jq 也就是先用 curl 拉取数据,然后丢进 jq 处理器解析。哪怕什么参数都不带,jq 默认格式化 JSON 数据,也能极大提升一个长行返回结果的接口的体验,而 jq 默认将结果染色,也能极大改善阅读 API 返回结果的体验。

sample-full-jq

除此以外,最常用的方法就是选择 JSON 文本的部分属性,筛选出感兴趣的内容。这在 REST API 总是无差别的返回大量内容的情况下尤为有用。

sample-transform-jq

HTTPie

HTTPie 是 Web API 工具中从终端走向 Web UI 的一个尝试。

它在功能集上和 curl 没有太大的区别,但是在参数的设计上进一步简化了常见场景的交互方式。HTTPie 的基本命令如下:

1
http [METHOD] URL [REQUEST_ITEM ...]

其中 METHOD 指的是 GET / POST / PUT / DELETE 这样的 HTTP 方法。如果命令不传递任何输入数据,则默认用 GET 方法发送请求,否则默认用 POST 方法发送请求。

为了进一步简化 Headers 和输入数据的体验,HTTPie 对 REQUEST_ITEM 做了一定的编码约定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
REQUEST_ITEM
Optional key-value pairs to be included in the request. The separator used
determines the type:

':' HTTP headers:

Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0

'==' URL parameters to be appended to the request URI:

search==httpie

'=' Data fields to be serialized into a JSON object (with --json, -j)
or form data (with --form, -f):

name=HTTPie language=Python description='CLI HTTP client'

':=' Non-string JSON data fields (only with --json, -j):

awesome:=true amount:=42 colors:='["red", "green", "blue"]'

'@' Form file fields (only with --form or --multipart):

cv@~/Documents/CV.pdf
cv@'~/Documents/CV.pdf;type=application/pdf'

'=@' A data field like '=', but takes a file path and embeds its content:

essay=@Documents/essay.txt

':=@' A raw JSON field like ':=', but takes a file path and embeds its content:

package:=@./package.json

You can use a backslash to escape a colliding separator in the field name:

field-name-with\:colon=value

此外,为了简化本地测试的键入内容,http :<port>/path 意味着向 localhost:<port>/path 发送请求。

最后,HTTPie 的输出默认就是以最佳阅读效果为目标上色的。

sample-full-httpie

可以看到,HTTPie 有很多专门为 Web API 开发者定制设计的特性,这也对得起它的定位:

HTTPie: modern, user-friendly command-line HTTP client for the API era.

HTTPie (pronounced aitch-tee-tee-pie) is a command-line HTTP client. Its goal is to make CLI interaction with web services as human-friendly as possible. HTTPie is designed for testing, debugging, and generally interacting with APIs & HTTP servers. The http & https commands allow for creating and sending arbitrary HTTP requests. They use simple and natural syntax and provide formatted and colorized output.

当然,跟 curl 对比起来,这样追求 human-friendly 的工具,自然也就不太适合机器或脚本自动生成规整的命令。

HTTPie 的团队提供了一个简易的 Web UI 工作台

webui-httpie

这个平台后端应该复用了 HTTPie Web 客户端的代码,但是跟命令行参数的解析应该就没什么关系了。这种通过 Web UI 填入参数和查看结果的做法,跟 Postman 的设计是高度一致的,适合不喜欢终端交互,更喜欢跟图形界面打交道的人。同时,Web UI 缓存用户数据的能力,也比命令行自己管理输入输出要省事儿不少。

Web API 平台

HTTPie 做 Web UI 是顺应 API 时代的潮流,在这个潮流下,大量的 Web API 平台应运而生。它们或者关注在 Web API 的设计,或者关注在开发和协作,或者关注在测试和自动化,或者关注在文档化。

除了测试和自动化大多是和通用框架结合,只是提供一些增强的构建块和测试断言,我认为没什么专门讲的价值,其余的几个主题本节各取一个代表介绍。

Swagger

严格来说,Swagger 是一个完整的 Web API 开发框架,不仅有 OpenAPI 标准定义 Web API 的接口协议,还有一整套工具生成代码、文档和在线调试。

但是 Swagger 最大的特色还是它的 OpenAPI 标准,即按照 REST API 的资源定义来描述当前 URL 下各个资源路径支持何种操作,传入参数和传出结果的类型和约束的一套标准。

例如,定义一个获取宠物信息的简单 REST 资源对应的 OpenAPI 配置片段如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
paths:
/pet:
put:
tags:
- pet
summary: Update an existing pet
description: Update an existing pet by Id
operationId: updatePet
requestBody:
description: Update an existent pet in the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
'405':
description: Validation exception

这些定义就像 Kubernetes Operator 领域里的 CRD 那样,通常都不是人写的,而是使用 swagger-core 一类的库先用某种程序设计语言写出 REST API 的定义和实现,再转成 OpenAPI 的定义。而后,如果要提供其他语言的客户端或服务端实现,再从 OpenAPI 标准的定义里生成对应的 stub 代码。

平台化开发、测试和自动文档化

OpenAPI 的生态非常繁荣,只要把 Web API 导出成 OpenAPI 的格式,基本就可以轻易地接入其他工具和平台做文档化、自动化测试和代码生成。

Postman

Postman 可以说是 API 开发和协同平台最有名的创新团队,目前 API 平台商业化的龙头。HTTPie 的 Web UI 基本是 Postman 的一个极简青春版。

Postman 最基本的功能是帮助开发者调试应用开发时需要访问的一众 API 并保存测试结果。进一步地,Postman 对常见的请求参数构造做了集成,帮助开发者更快速全面地测试 API 的可用性。最后,Postman 面向 API 的设计者推出了一系列辅助设计和管理 API 的功能。

Postman 的主页案例

Postman 的功能非常简单,但是它充当了连接开发者和服务提供方的重要桥梁。

在 Postman 上,你可以立即开始测试业务流程中对 Slack API 或者 Twitter API 的请求。国内的竞品 Apifox 抓住了这个重点,通过 API Hub 提供了企业微信 API 和钉钉 API 等等业务实际依赖的服务的在线调试界面。

webui-apifox

ReadMe

ReadMe 这个讨巧的域名对应的 API 平台服务专注于 API 文档及其用户旅程反馈。

上面提到的 Swagger 和 Postman 其实都提供了发布 API 文档和在线调试的能力,相较之下,ReadMe 提出的核心理念是:

Behind every API call is an API user. So why do your docs treat them all the same?

因此,它关注的不是 API 的开发,而是已有 API 并发布以后,用户实际在 API Doc 页面上调试的路径和正负反馈。

ReadMe 后台数据

API 开发的基础设施的意义

从面向黑客的命令行工具到 Web UI 开发设计平台,再到面向用户的 API 文档、测试和交流平台,Web API 的体验优化从提升开发者体验开始,最终惠及终端用户。

虽然 Web API 的体验最终是由用户定义的,但是开发 Web API 并改善其各方面体验的,是开发 API 的工程师。因此,提升他们的开发体验,改良他们手中的工具和开发平台,恰恰是优化用户体验的前提条件。

这跟手工艺者的工作变革是一致的:如果没有优良的机器和流水线,固然还会有少数能工巧匠能够做出高质量的工艺品,但是行业的平均水平和普通顾客能负担的产品整体质量是不足的。只有把最佳实践基础设施化,提高整个行业的基线,才能促进行业整体的进步。

开发 Web API 的工程师只有利用工具和平台节省在重复的实现和测试上的时间,才能有更多精力考虑如何设计出好的应用接口。软件应用之间通过好的接口集成组装,才有可能做出更加丰富的终端应用,最后改善终端用户的体验。

Web API 简介

Application Programming Interface (API) 即应用程序接口。顾名思义,它是开发者访问应用程序的接口。

例如,你可以通过以下命令查询 GitHub 上特定代码仓库的元数据信息:

1
curl https://api.github.com/repos/apache/pulsar

GitHub 会返回以下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"id": 62117812,
"name": "pulsar",
"full_name": "apache/pulsar",
"private": false,
"size": 185304,
"stargazers_count": 12577,
"watchers_count": 12577,
"language": "Java",
"visibility": "public",
"forks": 3269,
"open_issues": 1197,
"watchers": 12577,
"default_branch": "master",
// ...
}

从提供 API 的开发者的角度看,API 解决了一个常见的问题:如何将自己的代码开放给其他开发者去使用,以及如何让他们基于自己的代码进行创新。

现代软件开发的过程并非开发者完全从头编写每一行代码。相反,行业当中已经有相当数量的成熟应用提供公开服务。这些服务涵盖支付、通信、授权和身份认证等领域。在构建一个新软件时,软件工程师的工作就是使用这些 API 来组合新产品。重用他人的代码可以大大节省自己的时间,并且可以避免部分重复造轮子的操作。此外,使用业内约定俗成的公开服务,能够降低用户使用和其他应用集成的门槛。

Web API 的范畴

广义上的 API 包括太多内容,几乎只要是能够调用另一段代码的方法,都在 API 的范畴之内。甚至稍加琢磨,说不定能出一个 API 版本的阵营九宫格。

把范围缩小到 Web API 上来,这样的应用程序接口只会被调用方经由网络访问,这同时也意味着其访问方式是独立于特定编程语言的。

Orbit API 实例

例如,在上面的 API 实例中,右上角展示的访问该 API 的方式,基本是一个语言无关的 HTTP 请求,而使用各个语言通用的 HTTP client 库,就可以对这一 API 进行集成。

经常与狭义的 Web API 相混淆的,是 Software Development Kit (SDK) 即软件开发工具包。例如,GitHub 在语言无关的 REST API 的基础上,还提供了不同语言的 Octokit SDK 库:

通常,SDK 是 Web API 的精简抽象层。它们与特定编程语言的习惯和范式集成,帮助开发者略过原始的 Web API 调用细节利用接口。同时,与特定语言的集成使得开发者能够使用强类型系统和集成开发工具来调试应用程序。

不过,SDK 也有如果跟开发者常用的语言不符合,就很难发挥作用,以及如果不能保证和 API 提供的功能的同步,就会让开发者不得不 fallback 到 API 接口上的问题。例如,OpenDAL 在经过一段时间使用后,从 AWS S3 SDK 切换到纯粹 HTTP client 访问

say goodbye to aws-s3-sdk

Web API 的范式

狭义上的 Web API 支持客户端通过 HTTP 协议访问,而且通常请求和回复都是纯文本,尤其是 JSON 格式的纯文本。

gRPCApache Thrift 虽然也可以定义语言无关的应用程序接口,使用 HTTP 协议通信,但是由于其内容通常是不可读的字节流,且在当前的生态支持下几乎总是需要通过 SDK 来访问特定服务接口,所以并不属于典型的 Web API 范式。

REST API

REST API 是典型的 Web API 范式,甚至当提起 API 这个概念的时候,直觉的反应就是一组 REST API 定义。

REST API 定义了一系列可以访问的资源,并使用标准的 HTTP 方法来表示针对这些资源的创建(Create)、读取(Read)、更新(Update)和删除(Delete),即 CRUD 事务。

例如,GitHub REST API 将用户、组织、仓库、Issue 和 Pull Request 等等都表示为资源。以 Pull Request 为例,以下是不同操作在 HTTP 方法上的映射:

操作HTTP 方法URL (/pulls)URL (/pulls/{pull_number})
CreatePOST创建一个新的 Pull Request不支持
ReadGET列出所有 Pull Requests查询指定 Pull Request
UpdatePATCH未实现更新指定 Pull Request 状态

当然,还有很多的操作并不能够映射成 REST API 原教旨概念下的资源。这个时候的处理方式,要么是把操作写进请求字段里,要么是把操作视为一种资源,要么是把操作写到 URL 中间。

GitHub 提供的更新仓库的 API 支持传入 archive 参数代表把仓库归档。

github-rest-api-archive

GitHub 的 Pull Request API 有一个 merge “资源”来表示合并一个 Pull Request 的请求。

github-rest-api-merge

GitHub 提供了一个通用的查找接口来查询不同的资源,其请求采用 GET /search/code?q=:query 的形式指定查询的资源和模式。

github-rest-api-search

GraphQL

不同于上面介绍的 REST API 及其超越原教旨资源定义的范式,Facebook 开发了 GraphQL 查询语言。实现上,它也是对外暴露一个 HTTP 端点,例如 GitHub 的 GraphQL 端点可以通过以下方式访问:

1
curl -H "Authorization: bearer TOKEN" https://api.github.com/graphql

通过这个端点,GraphQL 把实际的查询或修改操作都收纳到了请求字段里,即以 querymutation 为最外层定义的一个层级结构。

我不想在这里介绍太多 GraphQL 的例子,因为它真的非常难用,而且开发生态奇差无比,除非迫不得已,否则当今的开发者应该没有使用 GraphQL 的理由。而这个迫不得已的理由就是 GitHub 对外暴露的 API 的一部分,只能使用 GraphQL 访问,或者使用 GraphQL 访问有巨大的优势。

这个巨大的优势就是 GraphQL 允许用户在一个请求当中做嵌套查询和跨资源获取数据,同时指定需要返回哪些字段。这样,一个用户视角的综合需求可以通过一次请求完成,同时跨网络传输的字节尽可能的少。只要你试过用 GitHub REST API 查询数据,你就会知道那些常用的端点它会返回多少你永远不会使用的字段数据。

REST API 可能返回大量用不上的字段

事件驱动型 API 范式

上面介绍的都是请求响应式的 Web API 范式,即客户端主动发起一个请求,服务器响应处理后返回结果,一次调用结束。

许多情况下,客户端程序会希望服务器在特定事件发生时主动向推送一个通知,或者保持一个全双工的连接,长时间地实时交互数据。

这些场景统称为事件驱动型 API 范式。相比于 REST API 及其扩展形式,它们的用例不够普遍,因此生态也较为薄弱,但是仍然比 GraphQL 要好上不少。

WebHook 是一种轻量级的事件驱动型 API 范式,用户注册一个 WebHook 回调的 URL 地址以及回调规则,服务器在发生匹配上规则的事件时,调用 WebHook URL 并发送事件内容。GitHub WebHook 以及其上的 GitHub Apps 就是在这个范式下工作的。

跟程序设计范式里通过 callback 组织控制流通常比 fallthrough 的控制流更难理解一样,理解 WebHook 的调用过程需要绕一个弯。GitHub Apps 在此基础上还要走一个多次 callback 的鉴权逻辑,过程就更加复杂。这里不做展开,可以跳转上面的链接自行尝试理解。

WebSocket 是一种全双工通信的 Web API 范式。客户端发送请求到服务端,建立起一个长连接,客户端和服务器可以同时发送和接收消息。

专注数据展示的低代码框架 Streamlit 就使用 WebSocket 来维持客户端和服务器的链接,既支持客户端请求新的数据和改变渲染逻辑,又支持服务器主动刷新数据结果并推送到客户端。

然而,WebSocket 对网络的稳定性有比较强的要求,如果链接中断,客户端需要重新启动并再次建立起长链接。如果你尝试在 Heroku 或 Google App Engine 这样 IP 经常漂移的环境部署 Streamlit 的实例,你就会发现服务经常卡死以至于几乎不可用。

最后一种事件驱动型 API 范式可以算作请求响应式 API 的扩展。HTTP Streaming 支持服务器对单个请求分批次响应无限程度的数据。

ChatGPT 底层的 OpenAI API 就支持 HTTP Streaming 的 API 范式,这也是你在网页端能够看到 ChatGPT 源源不断地返回结果,而不需要等待服务器计算出完整的响应,再一次性返回整个答复的技术原理。

openai-streaming

Web API 的体验

上面的举例过程中,无论是 Orbit 直接在网页端就可以尝试的文档,还是 GitHub 详尽的 REST API 解释和可复制粘贴的代码片段,再到 OpenAI 以 Jupyter Notebook 展示的使用案例,这些优秀的 Web API 提供商的实践说明了一件事:API 的使用体验至关重要。

构建可扩展、设计良好的 API 是一个良好的开始,这表示你对自己提供的应用程序系统有深入的理解和工程能力。但是,“只要构建好 API 用户就自然会使用”,这样的想法显然太过理想化。

开发者使用 API 的体验由许多部分构成,API 设计良好只是其中产品体验的一部分,针对不同层次的开发者,相应的文档内容体验和社群体验都需要投入建设。这个完整的构建开发者生态的工作被定义为开发者关系。

在专门讨论如何设计 Web API 的《Web API 设计》一书中,专门花了三个章节讨论这个问题。《开发者关系:方法与实践》则是专门讨论开发者关系工作的有益补充。我此前发布的《开发者体验的基础设施》也值得一读。

这里需要单独拿出来稍作展开的,是围绕 API 的设计、测试和发布构建的一系列 API 平台。API 平台是开发者体验基础设施的一环,它的出现是 API 的价值的反映,同时也代表了业界对 API 的设计跟整个生命周期认识的一个基准线。

其中最有名的当属在 2021 年融资 2.25 亿美元的 Postman 公司。它最基本的功能是帮助开发者调试应用开发时需要访问的一众 API 并保存测试结果。进一步地,Postman 对常见的请求参数构造做了集成,帮助开发者更快速全面地测试 API 的可用性。最后,Postman 面向 API 的设计者推出了一系列辅助设计和管理 API 的功能。

Postman 的主页案例

此外,Orbit 提供的可以在线交互调试的 API 文档页面也不是自研的,而是 readme.com 的产品。它在交付一整个 API 文档站的基础上,支持用户在线交互调试,并在后台记录这些交互数据,以帮助 API 的设计者得到用户真实的使用反馈。

最后,在这些商业支持的集成平台之外,类似 curl / httpie / jq 这样的开源命令行工具,以及 Swagger 这样的开放 Web API 定义标准和套件,也是 Web API 如此繁荣的重要生态支柱。

❌