Skip to content

Commit 2090cf7

Browse files
authored
return openai-defined type (#1580)
PR on top of #1576. I'm starting to think that it makes sense to define Zod schemas for inputs as we need to validate user's inputs. But that for outputs we "only" need static type checking and therefore we could reuse the types defined in https://github.com/openai/openai-node. **Benefits:** no need to redefine stuff manually. It's easy to make mistakes (a parameter that shouldn't be nullable, that could be an array, etc.) when translating from the specs to our codebase. If static typing don't complain then we can assume "it's good". Also less code to maintain. **Drawback:** less flexibility. We don't own the stack and things might get updated in the wild. It's less a problem in this context as it's a server and not a client (and therefore we manage the dependency updates). Overall I do think it's better to import from openai. Since we won't implement everything at first, it's fine to use `Omit<..., "key-that-we-dont-implement">` which **explicitly** removes a feature (better than implicit non-definition) --- **EDIT:** and it's fine to use them for now and if it's ever blocking in the future, then we redefine stuff ourselves.
1 parent 942fc8e commit 2090cf7

File tree

5 files changed

+28
-93
lines changed

5 files changed

+28
-93
lines changed

packages/responses-server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@
5353
"@huggingface/inference": "workspace:^",
5454
"@huggingface/tasks": "workspace:^",
5555
"express": "^4.18.2",
56+
"openai": "^5.8.2",
5657
"zod": "^3.22.4"
5758
},
5859
"devDependencies": {
5960
"@types/express": "^4.17.21",
60-
"openai": "^5.8.2",
6161
"tsx": "^4.7.0"
6262
}
6363
}

packages/responses-server/pnpm-lock.yaml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/responses-server/src/routes/responses.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { type Response as ExpressResponse } from "express";
22
import { type ValidatedRequest } from "../middleware/validation.js";
3-
import { type CreateResponse, type Response } from "../schemas.js";
3+
import { type CreateResponseParams } from "../schemas.js";
44
import { generateUniqueId } from "../lib/generateUniqueId.js";
55
import { InferenceClient } from "@huggingface/inference";
66
import type { ChatCompletionInputMessage, ChatCompletionInputMessageChunkType } from "@huggingface/tasks";
77

8+
import { type Response as OpenAIResponse } from "openai/resources/responses/responses";
9+
810
export const postCreateResponse = async (
9-
req: ValidatedRequest<CreateResponse>,
11+
req: ValidatedRequest<CreateResponseParams>,
1012
res: ExpressResponse
1113
): Promise<void> => {
1214
const apiKey = req.headers.authorization?.split(" ")[1];
@@ -60,7 +62,10 @@ export const postCreateResponse = async (
6062
top_p: req.body.top_p,
6163
});
6264

63-
const responseObject: Response = {
65+
const responseObject: Omit<
66+
OpenAIResponse,
67+
"incomplete_details" | "metadata" | "output_text" | "parallel_tool_calls" | "tool_choice" | "tools"
68+
> = {
6469
object: "response",
6570
id: generateUniqueId("resp"),
6671
status: "completed",
@@ -81,6 +86,7 @@ export const postCreateResponse = async (
8186
{
8287
type: "output_text",
8388
text: chatCompletionResponse.choices[0].message.content,
89+
annotations: [],
8490
},
8591
],
8692
},

packages/responses-server/src/schemas.ts

Lines changed: 13 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { z } from "zod";
44
* https://platform.openai.com/docs/api-reference/responses/create
55
* commented out properties are not supported by the server
66
*/
7-
export const createResponseSchema = z.object({
7+
export const createResponseParamsSchema = z.object({
88
// background: z.boolean().default(false),
99
// include:
1010
input: z.union([
@@ -22,15 +22,15 @@ export const createResponseSchema = z.object({
2222
}),
2323
z.object({
2424
type: z.literal("input_image"),
25-
// file_id: z.string().optional(),
25+
// file_id: z.string().nullable(),
2626
image_url: z.string(),
2727
// detail: z.enum(["auto", "low", "high"]).default("auto"),
2828
}),
2929
// z.object({
3030
// type: z.literal("input_file"),
31-
// file_data: z.string().optional(),
32-
// file_id: z.string().optional(),
33-
// filename: z.string().optional(),
31+
// file_data: z.string().nullable(),
32+
// file_id: z.string().nullable(),
33+
// filename: z.string().nullable(),
3434
// }),
3535
])
3636
),
@@ -46,97 +46,26 @@ export const createResponseSchema = z.object({
4646
// ])
4747
),
4848
]),
49-
instructions: z.string().optional(),
50-
// max_output_tokens: z.number().min(0).optional(),
51-
// max_tool_calls: z.number().min(0).optional(),
52-
// metadata: z.record(z.string().max(64), z.string().max(512)).optional(), // + 16 items max
49+
instructions: z.string().nullable(),
50+
// max_output_tokens: z.number().min(0).nullable(),
51+
// max_tool_calls: z.number().min(0).nullable(),
52+
// metadata: z.record(z.string().max(64), z.string().max(512)).nullable(), // + 16 items max
5353
model: z.string(),
54-
// previous_response_id: z.string().optional(),
54+
// previous_response_id: z.string().nullable(),
5555
// reasoning: z.object({
5656
// effort: z.enum(["low", "medium", "high"]).default("medium"),
57-
// summary: z.enum(["auto", "concise", "detailed"]).optional(),
57+
// summary: z.enum(["auto", "concise", "detailed"]).nullable(),
5858
// }),
5959
// store: z.boolean().default(true),
6060
// stream: z.boolean().default(false),
6161
temperature: z.number().min(0).max(2).default(1),
6262
// text:
6363
// tool_choice:
6464
// tools:
65-
// top_logprobs: z.number().min(0).max(20).optional(),
65+
// top_logprobs: z.number().min(0).max(20).nullable(),
6666
top_p: z.number().min(0).max(1).default(1),
6767
// truncation: z.enum(["auto", "disabled"]).default("disabled"),
6868
// user
6969
});
7070

71-
export type CreateResponse = z.infer<typeof createResponseSchema>;
72-
73-
export const responseSchema = z.object({
74-
object: z.literal("response"),
75-
created_at: z.number(),
76-
error: z
77-
.object({
78-
code: z.string(),
79-
message: z.string(),
80-
})
81-
.nullable(),
82-
id: z.string(),
83-
status: z.enum(["completed", "failed", "in_progress", "cancelled", "queued", "incomplete"]),
84-
// incomplete_details: z.object({ reason: z.string() }).optional(),
85-
instructions: z.string().optional(),
86-
// max_output_tokens: z.number().min(0).optional(),
87-
// max_tool_calls: z.number().min(0).optional(),
88-
// metadata: z.record(z.string().max(64), z.string().max(512)).optional(), // + 16 items max
89-
model: z.string(),
90-
output: z.array(
91-
z.object({
92-
type: z.enum(["message"]),
93-
id: z.string(),
94-
status: z.enum(["completed", "failed"]),
95-
role: z.enum(["assistant"]),
96-
content: z.array(
97-
z.union([
98-
z.object({
99-
type: z.literal("output_text"),
100-
text: z.string(),
101-
// annotations:
102-
// logprobs:
103-
}),
104-
z.object({
105-
type: z.literal("refusal"),
106-
refusal: z.string(),
107-
}),
108-
])
109-
),
110-
})
111-
// in practice, should be a z.union of the following:
112-
// File search tool call
113-
// Function tool call
114-
// Web search tool call
115-
// Computer tool call
116-
// Reasoning
117-
// Image generation call
118-
// Code interpreter tool call
119-
// Local shell call
120-
// MCP tool call
121-
// MCP list tools
122-
// MCP approval request
123-
),
124-
// parallel_tool_calls: z.boolean(),
125-
// previous_response_id: z.string().optional(),
126-
// reasoning: z.object({
127-
// effort: z.enum(["low", "medium", "high"]).optional(),
128-
// summary: z.enum(["auto", "concise", "detailed"]).optional(),
129-
// }),
130-
// store: z.boolean(),
131-
temperature: z.number(),
132-
// text:
133-
// tool_choice:
134-
// tools:
135-
// top_logprobs: z.number().int().min(0).max(20).optional(),
136-
top_p: z.number(),
137-
// truncation: z.enum(["auto", "disabled"]).default("disabled"),
138-
// usage: ...
139-
// user
140-
});
141-
142-
export type Response = z.infer<typeof responseSchema>;
71+
export type CreateResponseParams = z.infer<typeof createResponseParamsSchema>;

packages/responses-server/src/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import express, { type Express } from "express";
2-
import { createResponseSchema } from "./schemas.js";
2+
import { createResponseParamsSchema } from "./schemas.js";
33
import { validateBody } from "./middleware/validation.js";
44
import { requestLogger } from "./middleware/logging.js";
55
import { postCreateResponse } from "./routes/index.js";
@@ -16,7 +16,7 @@ export const createApp = (): Express => {
1616
res.send("hello world");
1717
});
1818

19-
app.post("/v1/responses", validateBody(createResponseSchema), postCreateResponse);
19+
app.post("/v1/responses", validateBody(createResponseParamsSchema), postCreateResponse);
2020

2121
return app;
2222
};

0 commit comments

Comments
 (0)