Skip to content

Commit 9ed1315

Browse files
authored
add openai file user message part and prompt build support (#760)
1 parent 0f80700 commit 9ed1315

10 files changed

Lines changed: 243 additions & 19 deletions

File tree

generated_types.json

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@
292292
"reasoning_effort": {
293293
"type": "string",
294294
"enum": [
295+
"none",
295296
"minimal",
296297
"low",
297298
"medium",
@@ -803,10 +804,47 @@
803804
},
804805
{
805806
"$ref": "#/components/schemas/ChatCompletionContentPartImageWithTitle"
807+
},
808+
{
809+
"$ref": "#/components/schemas/ChatCompletionContentPartFileWithTitle"
806810
}
807811
],
808812
"title": "chat_completion_content_part"
809813
},
814+
"ChatCompletionContentPartFileFile": {
815+
"type": "object",
816+
"properties": {
817+
"file_data": {
818+
"type": "string"
819+
},
820+
"filename": {
821+
"type": "string"
822+
},
823+
"file_id": {
824+
"type": "string",
825+
"title": "The ID of an uploaded file to use as input."
826+
}
827+
}
828+
},
829+
"ChatCompletionContentPartFileWithTitle": {
830+
"type": "object",
831+
"properties": {
832+
"file": {
833+
"$ref": "#/components/schemas/ChatCompletionContentPartFileFile"
834+
},
835+
"type": {
836+
"type": "string",
837+
"enum": [
838+
"file"
839+
]
840+
}
841+
},
842+
"required": [
843+
"file",
844+
"type"
845+
],
846+
"title": "file"
847+
},
810848
"ChatCompletionContentPartImageWithTitle": {
811849
"type": "object",
812850
"properties": {
@@ -3638,6 +3676,7 @@
36383676
"reasoning_effort": {
36393677
"type": "string",
36403678
"enum": [
3679+
"none",
36413680
"minimal",
36423681
"low",
36433682
"medium",
@@ -6615,7 +6654,7 @@
66156654
"license": {
66166655
"name": "Apache 2.0"
66176656
},
6618-
"x-internal-git-sha": "998df82d7471618f0c2e92a742c554b966e07055"
6657+
"x-internal-git-sha": "8e9c0a96b3cf291360978c17580f72f6817bd6c8"
66196658
},
66206659
"paths": {},
66216660
"webhooks": {}

js/scripts/generate_types.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ const TEMPLATE_PATH = path.join(
1313
"./openapi_zod_client_output_template.hbs",
1414
);
1515
const OUTPUT_PATH = path.join(SCRIPT_DIR, "../src/generated_types.ts");
16-
const OUTPUT_PATH2 = path.join(
17-
SCRIPT_DIR,
18-
"../../core/js/src/generated_types.ts",
19-
);
2016

2117
async function main() {
2218
const openApiDoc = JSON.parse(await fs.readFile(OPENAPI_SPEC_PATH, "utf-8"));
@@ -37,7 +33,6 @@ async function main() {
3733
const internalGitSha = openApiDoc.info["x-internal-git-sha"] || "UNKNOWN";
3834
const banner = `// Auto-generated file (internal git SHA ${internalGitSha}) -- do not modify\n\n`;
3935
await fs.writeFile(OUTPUT_PATH, banner + code);
40-
await fs.copyFile(OUTPUT_PATH, OUTPUT_PATH2);
4136
}
4237

4338
main();

js/src/generated_types.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Auto-generated file (internal git SHA 998df82d7471618f0c2e92a742c554b966e07055) -- do not modify
1+
// Auto-generated file (internal git SHA 8e9c0a96b3cf291360978c17580f72f6817bd6c8) -- do not modify
22

33
import { z } from "zod/v3";
44

@@ -105,7 +105,9 @@ export const AnyModelParams = z.object({
105105
.optional(),
106106
n: z.number().optional(),
107107
stop: z.array(z.string()).optional(),
108-
reasoning_effort: z.enum(["minimal", "low", "medium", "high"]).optional(),
108+
reasoning_effort: z
109+
.enum(["none", "minimal", "low", "medium", "high"])
110+
.optional(),
109111
verbosity: z.enum(["low", "medium", "high"]).optional(),
110112
top_k: z.number().optional(),
111113
stop_sequences: z.array(z.string()).optional(),
@@ -250,9 +252,23 @@ export const ChatCompletionContentPartImageWithTitle = z.object({
250252
export type ChatCompletionContentPartImageWithTitleType = z.infer<
251253
typeof ChatCompletionContentPartImageWithTitle
252254
>;
255+
export const ChatCompletionContentPartFileFile = z
256+
.object({ file_data: z.string(), filename: z.string(), file_id: z.string() })
257+
.partial();
258+
export type ChatCompletionContentPartFileFileType = z.infer<
259+
typeof ChatCompletionContentPartFileFile
260+
>;
261+
export const ChatCompletionContentPartFileWithTitle = z.object({
262+
file: ChatCompletionContentPartFileFile,
263+
type: z.literal("file"),
264+
});
265+
export type ChatCompletionContentPartFileWithTitleType = z.infer<
266+
typeof ChatCompletionContentPartFileWithTitle
267+
>;
253268
export const ChatCompletionContentPart = z.union([
254269
ChatCompletionContentPartTextWithTitle,
255270
ChatCompletionContentPartImageWithTitle,
271+
ChatCompletionContentPartFileWithTitle,
256272
]);
257273
export type ChatCompletionContentPartType = z.infer<
258274
typeof ChatCompletionContentPart
@@ -612,7 +628,7 @@ export const ModelParams = z.union([
612628
]),
613629
n: z.number(),
614630
stop: z.array(z.string()),
615-
reasoning_effort: z.enum(["minimal", "low", "medium", "high"]),
631+
reasoning_effort: z.enum(["none", "minimal", "low", "medium", "high"]),
616632
verbosity: z.enum(["low", "medium", "high"]),
617633
})
618634
.partial()

js/src/logger.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
/* eslint-disable @typescript-eslint/consistent-type-assertions */
2+
import type {
3+
ChatCompletionContentPartText,
4+
ChatCompletionContentPartImage,
5+
} from "openai/resources";
26
import { vi, expect, test, describe, beforeEach, afterEach } from "vitest";
37
import {
48
_exportsForTestingOnly,
@@ -13,6 +17,7 @@ import {
1317
Attachment,
1418
NOOP_SPAN,
1519
deepCopyEvent,
20+
renderMessage,
1621
} from "./logger";
1722
import { LazyValue } from "./util";
1823
import { configureNode } from "./node";
@@ -30,6 +35,64 @@ function getExportVersion(exportedSpan: string): number {
3035
return exportedBytes[0];
3136
}
3237

38+
test("renderMessage with file content parts", () => {
39+
const message = {
40+
role: "user" as const,
41+
content: [
42+
{
43+
type: "text" as const,
44+
text: "Here is a {{item}}:",
45+
},
46+
{
47+
type: "image_url" as const,
48+
image_url: {
49+
url: "{{image_url}}",
50+
},
51+
},
52+
{
53+
type: "file" as const,
54+
file: {
55+
file_data: "{{file_data}}",
56+
file_id: "{{file_id}}",
57+
filename: "{{filename}}",
58+
},
59+
},
60+
],
61+
};
62+
63+
const rendered = renderMessage(
64+
(template) =>
65+
template
66+
.replace("{{item}}", "document")
67+
.replace("{{image_url}}", "https://example.com/image.png")
68+
.replace("{{file_data}}", "base64data")
69+
.replace("{{file_id}}", "file-456")
70+
.replace("{{filename}}", "report.pdf"),
71+
message,
72+
);
73+
74+
expect(rendered.content).toEqual([
75+
{
76+
type: "text",
77+
text: "Here is a document:",
78+
},
79+
{
80+
type: "image_url",
81+
image_url: {
82+
url: "https://example.com/image.png",
83+
},
84+
},
85+
{
86+
type: "file",
87+
file: {
88+
file_data: "base64data",
89+
file_id: "file-456",
90+
filename: "report.pdf",
91+
},
92+
},
93+
]);
94+
});
95+
3396
test("verify MemoryBackgroundLogger intercepts logs", async () => {
3497
// Log to memory for the tests.
3598
_exportsForTestingOnly.simulateLoginForTests();

js/src/logger.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6235,6 +6235,19 @@ export function renderMessage<T extends Message>(
62356235
url: render(c.image_url.url),
62366236
},
62376237
};
6238+
case "file":
6239+
return {
6240+
...c,
6241+
file: {
6242+
file_data: render(c.file.file_data || ""),
6243+
...(c.file.file_id && {
6244+
file_id: render(c.file.file_id),
6245+
}),
6246+
...(c.file.filename && {
6247+
filename: render(c.file.filename),
6248+
}),
6249+
},
6250+
};
62386251
default:
62396252
const _exhaustiveCheck: never = c;
62406253
return _exhaustiveCheck;

py/src/braintrust/_generated_types.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,17 @@ class CallEventCallEvent7(TypedDict):
212212
]
213213

214214

215+
class ChatCompletionContentPartFileFile(TypedDict):
216+
file_data: NotRequired[Optional[str]]
217+
filename: NotRequired[Optional[str]]
218+
file_id: NotRequired[Optional[str]]
219+
220+
221+
class ChatCompletionContentPartFileWithTitle(TypedDict):
222+
file: ChatCompletionContentPartFileFile
223+
type: Literal['file']
224+
225+
215226
class ChatCompletionContentPartImageWithTitleImageUrl(TypedDict):
216227
url: str
217228
detail: NotRequired[Optional[Union[Literal['auto'], Literal['low'], Literal['high']]]]
@@ -342,7 +353,7 @@ class ChatCompletionTool(TypedDict):
342353

343354

344355
class CodeBundleRuntimeContext(TypedDict):
345-
runtime: Literal['node', 'python']
356+
runtime: Literal['node', 'python', 'browser']
346357
version: str
347358

348359

@@ -570,7 +581,7 @@ class Data(CodeBundle):
570581

571582

572583
class FunctionDataFunctionData1DataRuntimeContext(TypedDict):
573-
runtime: Literal['node', 'python']
584+
runtime: Literal['node', 'python', 'browser']
574585
version: str
575586

576587

@@ -649,7 +660,7 @@ class FunctionIdFunctionId3(TypedDict):
649660

650661

651662
class FunctionIdFunctionId4InlineContext(TypedDict):
652-
runtime: Literal['node', 'python']
663+
runtime: Literal['node', 'python', 'browser']
653664
version: str
654665

655666

@@ -668,7 +679,7 @@ class FunctionIdFunctionId4(TypedDict):
668679
FunctionIdRef = Mapping[str, Any]
669680

670681

671-
FunctionObjectType = Literal['prompt', 'tool', 'scorer', 'task', 'agent']
682+
FunctionObjectType = Literal['prompt', 'tool', 'scorer', 'task', 'agent', 'custom_view']
672683

673684

674685
FunctionOutputType = Literal['completion', 'score', 'any']
@@ -1854,7 +1865,7 @@ class AnyModelParams(TypedDict):
18541865
function_call: NotRequired[Optional[Union[Literal['auto'], Literal['none'], AnyModelParamsFunctionCall]]]
18551866
n: NotRequired[Optional[float]]
18561867
stop: NotRequired[Optional[Sequence[str]]]
1857-
reasoning_effort: NotRequired[Optional[Literal['minimal', 'low', 'medium', 'high']]]
1868+
reasoning_effort: NotRequired[Optional[Literal['none', 'minimal', 'low', 'medium', 'high']]]
18581869
verbosity: NotRequired[Optional[Literal['low', 'medium', 'high']]]
18591870
top_k: NotRequired[Optional[float]]
18601871
stop_sequences: NotRequired[Optional[Sequence[str]]]
@@ -1894,7 +1905,11 @@ class AttachmentStatus(TypedDict):
18941905
"""
18951906

18961907

1897-
ChatCompletionContentPart = Union[ChatCompletionContentPartTextWithTitle, ChatCompletionContentPartImageWithTitle]
1908+
ChatCompletionContentPart = Union[
1909+
ChatCompletionContentPartTextWithTitle,
1910+
ChatCompletionContentPartImageWithTitle,
1911+
ChatCompletionContentPartFileWithTitle,
1912+
]
18981913

18991914

19001915
class ChatCompletionMessageParamChatCompletionMessageParam1(TypedDict):
@@ -1993,6 +2008,14 @@ class DatasetEvent(TypedDict):
19932008
Whether this span is a root span
19942009
"""
19952010
origin: NotRequired[Optional[ObjectReferenceNullish]]
2011+
comments: NotRequired[Optional[Sequence[Any]]]
2012+
"""
2013+
Optional list of comments attached to this event
2014+
"""
2015+
audit_data: NotRequired[Optional[Sequence[Any]]]
2016+
"""
2017+
Optional list of audit entries attached to this event
2018+
"""
19962019

19972020

19982021
class Experiment(TypedDict):
@@ -2075,7 +2098,7 @@ class ModelParamsModelParams(TypedDict):
20752098
function_call: NotRequired[Optional[Union[Literal['auto'], Literal['none'], ModelParamsModelParamsFunctionCall]]]
20762099
n: NotRequired[Optional[float]]
20772100
stop: NotRequired[Optional[Sequence[str]]]
2078-
reasoning_effort: NotRequired[Optional[Literal['minimal', 'low', 'medium', 'high']]]
2101+
reasoning_effort: NotRequired[Optional[Literal['none', 'minimal', 'low', 'medium', 'high']]]
20792102
verbosity: NotRequired[Optional[Literal['low', 'medium', 'high']]]
20802103

20812104

@@ -2327,6 +2350,14 @@ class ExperimentEvent(TypedDict):
23272350
Whether this span is a root span
23282351
"""
23292352
origin: NotRequired[Optional[ObjectReferenceNullish]]
2353+
comments: NotRequired[Optional[Sequence[Any]]]
2354+
"""
2355+
Optional list of comments attached to this event
2356+
"""
2357+
audit_data: NotRequired[Optional[Sequence[Any]]]
2358+
"""
2359+
Optional list of audit entries attached to this event
2360+
"""
23302361

23312362

23322363
class GraphNodeGraphNode7(TypedDict):
@@ -2437,6 +2468,18 @@ class ProjectLogsEvent(TypedDict):
24372468
"""
24382469
span_attributes: NotRequired[Optional[SpanAttributes]]
24392470
origin: NotRequired[Optional[ObjectReferenceNullish]]
2471+
comments: NotRequired[Optional[Sequence[Any]]]
2472+
"""
2473+
Optional list of comments attached to this event
2474+
"""
2475+
audit_data: NotRequired[Optional[Sequence[Any]]]
2476+
"""
2477+
Optional list of audit entries attached to this event
2478+
"""
2479+
_async_scoring_state: NotRequired[Optional[Any]]
2480+
"""
2481+
The async scoring state for this event
2482+
"""
24402483

24412484

24422485
class ProjectScore(TypedDict):

py/src/braintrust/framework2.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from typing import Any, Callable, Dict, List, Optional, Union, overload
44

55
import slugify
6-
76
from braintrust.logger import api_conn, app_conn, login
87

98
from .framework import _is_lazy_load, bcolors # type: ignore

0 commit comments

Comments
 (0)