メインコンテンツにスキップ
OpenAI

2024年8月6日

企業

API に Structured Outputs を導入

API に Structured Outputs(構造化出力)を導入することにより、モデルの出力と開発者指定 JSON スキーマの一致が確保されます。

ブルー、グリーン、淡いイエローの様々な色合いの小さな正方形のパターンによる抽象的な画像です。正方形は格子状に配置され、やわらかなパステルカラーの色彩によるモザイク画に見える効果が生み出されています。

昨年、当社は DevDay において JSON モード — 当社モデルを使用して信頼性の高いアプリケーションの構築を企図される開発者の方に有用な構成要素 — を発表しました。JSON モードは有効な JSON 出力生成のためのモデルの信頼性を向上させますが、モデルの応答が特定のスキーマに準拠することを保証するものではありません。本日、API に導入される Structured Outputs について発表いたします。この新機能は、モデル生成の出力を開発者の方が指定した JSON スキーマと完全に一致させるために設計されています。

非構造化入力から構造化データを生成することは、現在の AI アプリケーションにおけるコアユースケースの1つです。開発者の方は、OpenAI API を用い、Function Calling(新しいウィンドウで開く) 機能を介してデータを取得し、質問に解答する能力を有する強力なアシスタントを構築します。この機能は、データ入力用の構造化データを抽出し、LLM がアクションを実行できるようにする複数ステップのエージェントワークフローを構築します。長年の間、開発者の方はこの分野の LLM にある制限に対して、モデルの出力をシステムとの相互運用に必要な形式と一致させるためにオープンソースツール利用、プロンプト、リクエスト再試行を繰り返すという調整措置をとってきました。Structured Outputs は、開発者の方が指定したスキーマと一致するように OpenAI モデルを制約すること、および複雑なスキーマをより良く理解するように当社モデルに学習させることで、この問題を解決しました。

複雑な JSON スキーマに対する準拠率を当社が評価したところ、Structured Outputs を導入した当社新モデル gpt-4o-2024-08-06 は完璧な100%というスコアを出しました。これと比較して、gpt-4-0613 のスコアは40%未満です。

Structured Outputs を用いた gpt-4o-2024-08-06 は当社評価においてスキーマに完全に準拠した出力である100%の信頼性を出しました。

Structured Outputs の使い方

Structured Outputs は以下の2形体で API に導入されました。 

1.Function Calling(関数呼び出し):Structured Outputs は、ツール経由での機能定義内で strict: true を設定することで利用できます。この機能は、ツールをサポートする gpt-4-0613gpt-3.5-turbo-0613 以降のすべてのモデルで動作します。Structured Outputs が有効化されている場合、指定されたツール定義と一致したモデル出力となります。

JSON

1
POST /v1/chat/completions
2
{
3
"model": "gpt-4o-2024-08-06",
4
"messages": [
5
{
6
"role": "system",
7
"content": "You are a helpful assistant. The current date is August 6, 2024. You help users query for the data they are looking for by calling the query function."
8
},
9
{
10
"role": "user",
11
"content": "look up all my orders in may of last year that were fulfilled but not delivered on time"
12
}
13
],
14
"tools": [
15
{
16
"type": "function",
17
"function": {
18
"name": "query",
19
"description": "Execute a query.",
20
"strict": true,
21
"parameters": {
22
"type": "object",
23
"properties": {
24
"table_name": {
25
"type": "string",
26
"enum": ["orders"]
27
},
28
"columns": {
29
"type": "array",
30
"items": {
31
"type": "string",
32
"enum": [
33
"id",
34
"status",
35
"expected_delivery_date",
36
"delivered_at",
37
"shipped_at",
38
"ordered_at",
39
"canceled_at"
40
]
41
}
42
},
43
"conditions": {
44
"type": "array",
45
"items": {
46
"type": "object",
47
"properties": {
48
"column": {
49
"type": "string"
50
},
51
"operator": {
52
"type": "string",
53
"enum": ["=", ">", "<", ">=", "<=", "!="]
54
},
55
"value": {
56
"anyOf": [
57
{
58
"type": "string"
59
},
60
{
61
"type": "number"
62
},
63
{
64
"type": "object",
65
"properties": {
66
"column_name": {
67
"type": "string"
68
}
69
},
70
"required": ["column_name"],
71
"additionalProperties": false
72
}
73
]
74
}
75
},
76
"required": ["column", "operator", "value"],
77
"additionalProperties": false
78
}
79
},
80
"order_by": {
81
"type": "string",
82
"enum": ["asc", "desc"]
83
}
84
},
85
"required": ["table_name", "columns", "conditions", "order_by"],
86
"additionalProperties": false
87
}
88
}
89
}
90
]
91
}

2.response_format パラメーターの新しいオプション:開発者の方は、json_schema という response_format パラメーターの新オプションを介して JSON スキーマを指定できるようになりました。これは、モデルがツールを呼び出すのではなく、構造化された方法でユーザーに応答する場合に有用です。この機能は、本日リリースの GPT‑4o 最新モデル gpt-4o-2024-08-06 および gpt-4o-mini-2024-07-18 で動作します。response_formatstrict: true と共に指定されると、指定されたスキーマと一致したモデル出力となります。

リクエスト

1
POST /v1/chat/completions
2
{
3
"model": "gpt-4o-2024-08-06",
4
"messages": [
5
{
6
"role": "system",
7
"content": "You are a helpful math tutor."
8
},
9
{
10
"role": "user",
11
"content": "solve 8x + 31 = 2"
12
}
13
],
14
"response_format": {
15
"type": "json_schema",
16
"json_schema": {
17
"name": "math_response",
18
"strict": true,
19
"schema": {
20
"type": "object",
21
"properties": {
22
"steps": {
23
"type": "array",
24
"items": {
25
"type": "object",
26
"properties": {
27
"explanation": {
28
"type": "string"
29
},
30
"output": {
31
"type": "string"
32
}
33
},
34
"required": ["explanation", "output"],
35
"additionalProperties": false
36
}
37
},
38
"final_answer": {
39
"type": "string"
40
}
41
},
42
"required": ["steps", "final_answer"],
43
"additionalProperties": false
44
}
45
}
46
}
47
}

Structured Outputs の安全性

安全性は OpenAI の最優先事項です。この新機能 Structured Outputs は、当社既存の安全ポリシーを遵守し、安全でないリクエストをモデルが拒否するようにもしています。開発を簡易化するため、API 応答に新たに refusal(拒否)文字列値が追加されました。これにより開発者の方は、モデルがスキーマに一致する出力の代わりに拒否を生成したかをプログラム的に検出できます。応答に拒否が含まれておらず、モデルの応答が途中で中断(finish_reason で表示)されていない場合には、モデルの応答では指定されたスキーマに一致する有効な JSON が確実に生成されます。

JSON

1
{
2
"id": "chatcmpl-9nYAG9LPNonX8DAyrkwYfemr3C8HC",
3
"object": "chat.completion",
4
"created": 1721596428,
5
"model": "gpt-4o-2024-08-06",
6
"choices": [
7
{
8
"index": 0,
9
"message": {
10
"role": "assistant",
11
"refusal": "I'm sorry, I cannot assist with that request."
12
},
13
"logprobs": null,
14
"finish_reason": "stop"
15
}
16
],
17
"usage": {
18
"prompt_tokens": 81,
19
"completion_tokens": 11,
20
"total_tokens": 92
21
},
22
"system_fingerprint": "fp_3407719c7f"
23
}

SDK のネイティブサポート

OpenAI の Python と Node SDK は、Structured Outputs のネイティブサポートのためにアップデートされました。ツールまたは応答形式としてのスキーマの指定は、Pydantic または Zod のオブジェクトを指定するだけという簡単なことです。SDK は、サポートされている JSON スキーマへのデータ型の変換、JSON 応答の型付きデータ構造への自動的逆シリアル化、および拒否が発生した場合の構文解析を処理します。

Function Calling による Structured Outputs のネイティブサポートの例を下に示します。

Python

1
from enum import Enum
2
from typing import Union
3

4
from pydantic import BaseModel
5

6
import openai
7
from openai import OpenAI
8

9

10
class Table(str, Enum):
11
orders = "orders"
12
customers = "customers"
13
products = "products"
14

15

16
class Column(str, Enum):
17
id = "id"
18
status = "status"
19
expected_delivery_date = "expected_delivery_date"
20
delivered_at = "delivered_at"
21
shipped_at = "shipped_at"
22
ordered_at = "ordered_at"
23
canceled_at = "canceled_at"
24

25

26
class Operator(str, Enum):
27
eq = "="
28
gt = ">"
29
lt = "<"
30
le = "<="
31
ge = ">="
32
ne = "!="
33

34

35
class OrderBy(str, Enum):
36
asc = "asc"
37
desc = "desc"
38

39

40
class DynamicValue(BaseModel):
41
column_name: str
42

43

44
class Condition(BaseModel):
45
column: str
46
operator: Operator
47
value: Union[str, int, DynamicValue]
48

49

50
class Query(BaseModel):
51
table_name: Table
52
columns: list[Column]
53
conditions: list[Condition]
54
order_by: OrderBy
55

56

57
client = OpenAI()
58

59
completion = client.beta.chat.completions.parse(
60
model="gpt-4o-2024-08-06",
61
messages=[
62
{
63
"role": "system",
64
"content": "You are a helpful assistant. The current date is August 6, 2024. You help users query for the data they are looking for by calling the query function.",
65
},
66
{
67
"role": "user",
68
"content": "look up all my orders in may of last year that were fulfilled but not delivered on time",
69
},
70
],
71
tools=[
72
openai.pydantic_function_tool(Query),
73
],
74
)
75

76
print(completion.choices[0].message.tool_calls[0].function.parsed_arguments)

Structured Outputs のネイティブサポートは response_format でも利用可能です。

Python

1
from pydantic import BaseModel
2

3
from openai import OpenAI
4

5

6
class Step(BaseModel):
7
explanation: str
8
output: str
9

10

11
class MathResponse(BaseModel):
12
steps: list[Step]
13
final_answer: str
14

15

16
client = OpenAI()
17

18
completion = client.beta.chat.completions.parse(
19
model="gpt-4o-2024-08-06",
20
messages=[
21
{"role": "system", "content": "You are a helpful math tutor."},
22
{"role": "user", "content": "solve 8x + 31 = 2"},
23
],
24
response_format=MathResponse,
25
)
26

27
message = completion.choices[0].message
28
if message.parsed:
29
print(message.parsed.steps)
30
print(message.parsed.final_answer)
31
else:
32
print(message.refusal)

その他のユースケース

開発者の方には、様々なユースケースにおいて構造化データの生成のために OpenAI のモデルをよくご利用いただいています。その他のユースケースの例:

ユーザーの意図に基づくユーザーインターフェースの動的生成

コードまたは UI を生成するアプリケーションを作成するための Structured Outputs の利用などです。以下の例は、すべて同じ response_format を用いており、ユーザーの入力に基づいて様々な UI の生成が可能になっています。

システム
あなたはユーザーインターフェースアシスタントです。あなたの仕事は、ユーザーがウェブサイトやアプリのアイデアを視覚化できるようにサポートすることです。
応答形式
アシスタント
1
{
2
"type": "div",
3
"label": "",
4
"children": [
5
{
6
"type": "header",
7
"label": "",
8
"children": [
9
{
10
"type": "div",
11
"label": "Green Thumb Gardening",
12
"children": [],
13
"attributes": [{ "name": "className", "value": "site-title" }]
14
},
15
{
16
"type": "div",
17
"label": "Bringing Life to Your Garden",
18
"children": [],
19
"attributes": [{ "name": "className", "value": "site-tagline" }]
20
}
21
],
22
"attributes": [{ "name": "className", "value": "header" }]
23
},
24
{
25
"type": "section",
26
"label": "",
27
"children": [
28
{
29
"type": "div",
30
"label": "",
31
"children": [
32
{
33
"type": "div",
34
"label": "About Us",
35
"children": [
36
{
37
"type": "div",
38
"label": "At Green Thumb Gardening, we specialize in transforming your outdoor spaces into beautiful, thriving gardens. Our team has decades of experience in horticulture and landscape design.",
39
"children": [],
40
"attributes": [
41
{ "name": "className", "value": "about-description" }
42
]
43
}
44
],
45
"attributes": [{ "name": "className", "value": "about-section" }]
46
}
47
],
48
"attributes": [{ "name": "className", "value": "content" }]
49
}
50
],
51
"attributes": [{ "name": "className", "value": "about-container" }]
52
},
53
{
54
"type": "section",
55
"label": "",
56
"children": [
57
{
58
"type": "div",
59
"label": "",
60
"children": [
61
{
62
"type": "div",
63
"label": "Our Services",
64
"children": [
65
{
66
"type": "div",
67
"label": "Garden Design",
68
"children": [],
69
"attributes": [
70
{ "name": "className", "value": "service-item" }
71
]
72
},
73
{
74
"type": "div",
75
"label": "Plant Care & Maintenance",
76
"children": [],
77
"attributes": [
78
{ "name": "className", "value": "service-item" }
79
]
80
},
81
{
82
"type": "div",
83
"label": "Seasonal Cleanup",
84
"children": [],
85
"attributes": [
86
{ "name": "className", "value": "service-item" }
87
]
88
},
89
{
90
"type": "div",
91
"label": "Custom Landscaping",
92
"children": [],
93
"attributes": [
94
{ "name": "className", "value": "service-item" }
95
]
96
}
97
],
98
"attributes": [{ "name": "className", "value": "services-list" }]
99
}
100
],
101
"attributes": [{ "name": "className", "value": "content" }]
102
}
103
],
104
"attributes": [{ "name": "className", "value": "services-container" }]
105
}
106
],
107
"attributes": [{ "name": "className", "value": "landing-page" }]
108
}
109

Welcome to Green Thumb Gardening
Bringing Life to Your Garden
At Green Thumb Gardening, we specialize in transforming your outdoor spaces into beautiful, thriving gardens. Our team has decades of experience in horticulture and landscape design.
Our services
Garden Design
Plant Care & Maintenance
Seasonal Cleanup
Custom Landscaping

裏付けとなる論理的思考や補足コメントと最終的回答の分離

思考の連鎖のための別のフィールドをモデルに与えると、最終的解答の質の向上に役立ちます。

JSON

1
{
2
"model": "gpt-4o-2024-08-06",
3
"messages": [
4
{
5
"role": "system",
6
"content": "You are a helpful assistant"
7
},
8
{
9
"role": "user",
10
"content": "9.11 and 9.9 -- which is bigger?"
11
}
12
],
13
"response_format": {
14
"type": "json_schema",
15
"json_schema": {
16
"name": "reasoning_schema",
17
"strict": true,
18
"schema": {
19
"type": "object",
20
"properties": {
21
"reasoning_steps": {
22
"type": "array",
23
"items": {
24
"type": "string"
25
},
26
"description": "The reasoning steps leading to the final conclusion."
27
},
28
"answer": {
29
"type": "string",
30
"description": "The final answer, taking into account the reasoning steps."
31
}
32
},
33
"required": ["reasoning_steps", "answer"],
34
"additionalProperties": false
35
}
36
}
37
}
38
}

非構造化データからの構造化データ抽出

会議メモから ToDo リスト、期限、割り当てなどを抽出するためのモデルへの指示などです。

JSON

1
POST /v1/chat/completions
2
{
3
"model": "gpt-4o-2024-08-06",
4
"messages": [
5
{
6
"role": "system",
7
"content": "Extract action items, due dates, and owners from meeting notes."
8
},
9
{
10
"role": "user",
11
"content": "...meeting notes go here..."
12
}
13
],
14
"response_format": {
15
"type": "json_schema",
16
"json_schema": {
17
"name": "action_items",
18
"strict": true,
19
"schema": {
20
"type": "object",
21
"properties": {
22
"action_items": {
23
"type": "array",
24
"items": {
25
"type": "object",
26
"properties": {
27
"description": {
28
"type": "string",
29
"description": "Description of the action item."
30
},
31
"due_date": {
32
"type": ["string", "null"],
33
"description": "Due date for the action item, can be null if not specified."
34
},
35
"owner": {
36
"type": ["string", "null"],
37
"description": "Owner responsible for the action item, can be null if not specified."
38
}
39
},
40
"required": ["description", "due_date", "owner"],
41
"additionalProperties": false
42
},
43
"description": "List of action items from the meeting."
44
}
45
},
46
"required": ["action_items"],
47
"additionalProperties": false
48
}
49
}
50
}
51
}

内容の詳細

JSON スキーマに一致するようモデル出力の信頼性を向上させるために、2パートから成るアプローチをとりました。まず、当社最新モデルの gpt-4o-2024-08-06 に複雑なスキーマの理解と、それに一致する出力生成のために最適な方法を学習させました。しかし、モデルの挙動は本質的に非決定論的なものでした。モデルのパフォーマンスは向上しました(当社ベンチマークで93%でした)が、ロバストなアプリケーション構築のために開発者の方が必要とする信頼性を満たすものではありませんでした。そこで、モデルの出力に制約を加えて100%の信頼性を達成するために、エンジニアリングベースの決定論的アプローチも用いることにしました。

制約付きデコード

当社がとったアプローチは、制約付きサンプリングや制約付きデコードと呼ばれる技術に基づくものです。デフォルトでは、出力生成のためにサンプリングを行う場合にモデルには全く制約がなく、次の出力として語彙から任意のトークンを選択できます。この柔軟性のために、モデルは間違いを犯しやすくなってしまいます。例えば、有効な JSON が生成されない場合であっても、通常では中括弧トークンを自由にサンプリングしてしまうのです。有効な出力を確保するために、利用可能なすべてのトークンではなく、指定されたスキーマに基づいて有効となるトークンのみをサンプリングするようにモデルに制約を与えました。

モデルの出力の全体においてでは有効となるトークンが異なってくるため、実際にこの制約を実装するのは難しい場合があります。例として、以下のスキーマを見てみましょう。

JSON

1
{
2
"type": "object",
3
"properties": {
4
"value": { "type": "number" }
5
},
6
"required": ["value"],
7
"additionalProperties": false
8
}

この出力の始めでは、{{“{
などが有効なトークンとなっています。しかし、モデルが{“valをサンプリングした後には、{は有効なトークンではなくなってしまいます。そこで、動的な制約付きデコードを実装し、予め応答の開始時に判断しておくのではなく、各トークンの生成後にそれが有効であるかを判断する必要があります。

これを行うため、指定の JSON スキーマを文脈自由文法(CFG)に変換しました。文法とは言語を定義する一連の規則であり、文脈自由文法は特定の規則に従う文法です。JSON および JSON スキーマは、言語内で有効なものを定義する規則を持つ文法と考えることができます。英語において動詞がないと文として成り立たない(有効でない)ように、JSON においては末尾にコンマがあることは有効ではありません。

そのため各々の JSON スキーマに対して、それを規定する文法を計算して、モデルのサンプリング中に簡単にアクセスできるようにコンポーネントを前処理します。これが、新しいスキーマを使用した最初のリクエストで遅延ペナルティが発生する理由です。サンプリング中に効率的に使用できるアーティファクトを生成するためには、スキーマを前処理しなければなりません。

サンプリング中、当社の推論エンジンは以前に生成されたトークンおよび文法内の規則(次に有効なトークンを規定)に基づいて、各トークンの生成後に、それが有効であるかを判断します。次に、このトークンのリストを使用して次のサンプリングステップをマスクします。これにより無効なトークンの生成率が結果的に下がって、0になります。スキーマを前処理しているため、キャッシュされたデータ構造を使用したマスキングが効率的に実行可能となり、遅延オーバーヘッドを最小限に抑えられます。

代替アプローチ

この問題に対する代替アプローチでは、制約付きデコードのために有限状態機械(FSM)や Regex(通常、FSM に実装)がよく用いられます。これらのアプローチは、各トークンの生成後にそのトークンの有効性を動的に更新するという点では同様に機能しますが、CFG アプローチとはいくつか重要な違いがあります。とりわけ、CFG は FSM よりも幅広いクラスの言語を表現できることが挙げられます。実際に、これは上述の value スキーマのようなごく単純なスキーマでは問題となりません。しかし、ネストされたデータ構造や再帰的データ構造などのもっと複雑なスキーマにおいては、この違いが意味を持ちます。例えば、通常、FSM は再帰的データ構造を表現できないため、FSM ベースのアプローチでは深くネストされた JSON の括弧を一致させるのが難しい場合があります。下記は、Structured Outputs が導入された OpenAI API ではサポートされているけれども、FSM では表現できない再帰的スキーマの例です。

JSON

1
{
2
"name": "ui",
3
"description": "Dynamically generated UI",
4
"strict": true,
5
"schema": {
6
"type": "object",
7
"properties": {
8
"type": {
9
"type": "string",
10
"description": "The type of the UI component",
11
"enum": ["div", "button", "header", "section", "field", "form"]
12
},
13
"label": {
14
"type": "string",
15
"description": "The label of the UI component, used for buttons or form fields"
16
},
17
"children": {
18
"type": "array",
19
"description": "Nested UI components",
20
"items": {
21
"$ref": "#"
22
}
23
},
24
"attributes": {
25
"type": "array",
26
"description": "Arbitrary attributes for the UI component, suitable for any element",
27
"items": {
28
"type": "object",
29
"properties": {
30
"name": {
31
"type": "string",
32
"description": "The name of the attribute, for example onClick or className"
33
},
34
"value": {
35
"type": "string",
36
"description": "The value of the attribute"
37
}
38
}
39
}
40
}
41
},
42
"required": ["type", "label", "children", "attributes"],
43
"additionalProperties": false
44
}
45
}

なお、各 UI 要素には、ルートスキーマを再帰的に参照する任意の子要素を含めることができます。CFG アプローチが可能にするものが、この柔軟性なのです。

制限・制約

Structured Outputs をご利用いただく際は、以下の制限事項にご留意ください。

  • Structured Outputs は、当社ドキュメント(新しいウィンドウで開く)に説明されている JSON スキーマのサブセットに対して利用した場合にのみ、可能な限り最高のパフォーマンスが確保されます。
  • 新しいスキーマを使用した際の API の最初の応答には追加の遅延が発生しますが、その後は遅延ペナルティのない高速な応答になります。これは、上述のように最初のリクエスト中にスキーマを処理して、後で高速に再利用できるようにアーティファクトがキャッシュされるためです。最初のリクエストにおける処理時間は、一般的なスキーマでは10秒未満ですが、より複雑なスキーマでは最大1分となる場合があります。
  • 安全でないリクエストに対してモデルが拒否を選択した場合には、モデルがスキーマに従わないことがあり得ます。拒否が選択された場合、それを示す refusal ブール値が true に設定された返答メッセージとなります。 
  • 生成が完了する前に max_tokens などの停止条件に達した場合には、モデルがスキーマに従わないことがあり得ます。 
  • Structured Outputs は、モデルのすべてのタイプの間違いを防げるわけではありません。例えば、JSON オブジェクトの値において間違いを犯す(数学の方程式でステップを間違えるなど)可能性が依然としてあります。間違いが見つかった場合、システムの指示に例を提供するか、より単純なサブタスクにタスクを分割することが開発者の方には推奨されます。
  • Structured Outputs は、並列関数呼び出しとは互換性がありません。並列関数呼び出しが生成されると、指定されたスキーマと一致しない可能性があります。並列関数呼び出しを無効にするには、parallel_tool_calls: false を設定してください。
  • Structured Outputs で指定された JSON スキーマはゼロデータ保持(新しいウィンドウで開く)(ZDR)の対象ではありません。

可用性

現在、Structured Outputs は API で公開されており、利用可能です。 

Function Calling(関数呼び出し)での Structured Outputs の利用は、API で Function Calling をサポートするすべてのモデルで可能です。これには、最新モデル(gpt-4ogpt-4o-mini)、gpt-4-0613gpt-3.5-turbo-0613 以降のすべてのモデル、および Function Calling をサポートするファインチューニングがされたモデルが含まれます。本機能は Chat Completions API、Assistants API、Batch API で利用可能です。また、Function Calling での Structured Outputs 利用は、Vision 入力とも互換性があります。

response_format での Structured Outputs の利用は、gpt-4o-minigpt-4o-2024-08-06 およびこれらに基づくファインチューニングモデルで可能です。本機能は Chat Completions API、Assistants API、Batch API で利用可能です。また、response_format での Structured Outputs 利用は、Vision 入力とも互換性があります。 

開発者の方は、新モデルの gpt-4o-2024-08-06 に切り替えることで、gpt-4o-2024-05-13 と比較して、入力で50%($2.50/100万入力トークン)、出力で33%%($10.00/100万出力トークン)のコスト節減となります。

Structured Outputs の利用開始には、当社ドキュメント(新しいウィンドウで開く)をご確認ください。 

謝辞

Structured Outputs は、オープンソースコミュニティの優れた研究、具体的には outlines(新しいウィンドウで開く)jsonformer(新しいウィンドウで開く)instructor(新しいウィンドウで開く)guidance(新しいウィンドウで開く)lark(新しいウィンドウで開く) ライブラリからインスピレーションを得ています。

著者

Michelle Pokrass

主力貢献者

Chris Colby、Melody Guan、Michelle Pokrass、Ted Sanders、Brian Zhang

謝辞

John Allard、Filipe de Avila Belbute Peres、Ilan Bigio、Owen Campbell-Moore、Chen Ding、Atty Eleti、Elie Georges、Katia Gil Guzman、Jeff Harris、Johannes Heidecke、Beth Hoover、Romain Huet、Tomer Kaftan、Jillian Khoo、Karolis Kosas、Ryan Liu、Kevin Lu、Lindsay McCallum、Rohan Nuttall、Joe Palermo、Leher Pathak、Ishaan Singal、Felipe Petroski Such、Freddie Sulit、David Weedon