Skip to content

Commit da8b897

Browse files
authored
Feature/structured streamable output (#7)
* Add streamed structured output and split messaging internals * Refactor structured stream parser loop
1 parent 5b2bf65 commit da8b897

22 files changed

+2062
-233
lines changed

DemoApp/AssistantRuntimeDemoApp/Shared/AgentDemoViewModel+StructuredOutput.swift

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,98 @@ extension AgentDemoViewModel {
9999
lastError = error.localizedDescription
100100
}
101101
}
102+
103+
func runStreamedStructuredOutputDemo() async {
104+
guard session != nil else {
105+
lastError = "Sign in before running the streamed structured output demo."
106+
return
107+
}
108+
guard !isRunningStructuredStreamingDemo else {
109+
return
110+
}
111+
112+
isRunningStructuredStreamingDemo = true
113+
lastError = nil
114+
structuredStreamingResult = nil
115+
structuredStreamingError = nil
116+
defer {
117+
isRunningStructuredStreamingDemo = false
118+
}
119+
120+
do {
121+
let thread = try await runtime.createThread(
122+
title: "Structured Output: Streamed Delivery Update",
123+
personaStack: Self.supportPersona
124+
)
125+
let request = DemoStructuredOutputExamples.streamedStructuredRequest()
126+
if showResolvedInstructionsDebug {
127+
lastResolvedInstructions = try await runtime.resolvedInstructionsPreview(
128+
for: thread.id,
129+
request: request
130+
)
131+
lastResolvedInstructionsThreadTitle = thread.title ?? "Structured Output: Streamed Delivery Update"
132+
}
133+
134+
let stream = try await runtime.streamMessage(
135+
request,
136+
in: thread.id,
137+
expecting: StreamedStructuredDeliveryUpdate.self
138+
)
139+
140+
var visibleText = ""
141+
var partialSnapshots: [StreamedStructuredDeliveryUpdate] = []
142+
var committedPayload: StreamedStructuredDeliveryUpdate?
143+
144+
for try await event in stream {
145+
switch event {
146+
case let .assistantMessageDelta(_, _, delta):
147+
visibleText += delta
148+
149+
case let .messageCommitted(message):
150+
if message.role == .assistant {
151+
visibleText = message.displayText
152+
}
153+
154+
case let .structuredOutputPartial(partial):
155+
if partialSnapshots.last != partial {
156+
partialSnapshots.append(partial)
157+
}
158+
159+
case let .structuredOutputCommitted(payload):
160+
committedPayload = payload
161+
162+
case let .turnFailed(error):
163+
throw error
164+
165+
default:
166+
break
167+
}
168+
}
169+
170+
let messages = await runtime.messages(for: thread.id)
171+
let persistedMetadata = messages.last(where: { $0.role == .assistant })?.structuredOutput
172+
173+
guard let committedPayload else {
174+
throw AgentRuntimeError.structuredOutputMissing(
175+
formatName: StreamedStructuredDeliveryUpdate.responseFormat.name
176+
)
177+
}
178+
179+
structuredStreamingResult = StructuredStreamingDemoResult(
180+
threadID: thread.id,
181+
threadTitle: thread.title ?? "Structured Output: Streamed Delivery Update",
182+
prompt: DemoStructuredOutputExamples.streamedStructuredPrompt,
183+
visibleText: visibleText.trimmingCharacters(in: .whitespacesAndNewlines),
184+
partialSnapshots: partialSnapshots,
185+
committedPayload: committedPayload,
186+
persistedMetadata: persistedMetadata
187+
)
188+
threads = await runtime.threads()
189+
activeThreadID = thread.id
190+
setMessages(messages)
191+
} catch {
192+
structuredStreamingError = error.localizedDescription
193+
lastError = error.localizedDescription
194+
}
195+
}
102196
}

DemoApp/AssistantRuntimeDemoApp/Shared/AgentDemoViewModel.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ struct StructuredOutputDemoImportResult: Sendable {
3939
let summary: StructuredImportedContentSummary
4040
}
4141

42+
struct StructuredStreamingDemoResult: Sendable {
43+
let threadID: String
44+
let threadTitle: String
45+
let prompt: String
46+
let visibleText: String
47+
let partialSnapshots: [StreamedStructuredDeliveryUpdate]
48+
let committedPayload: StreamedStructuredDeliveryUpdate
49+
let persistedMetadata: AgentStructuredOutputMetadata?
50+
}
51+
4252
struct GuidedMemoryDemoResult: Sendable {
4353
let record: MemoryRecord
4454
let diagnostics: MemoryStoreDiagnostics
@@ -135,8 +145,11 @@ final class AgentDemoViewModel: @unchecked Sendable {
135145
var pendingComposerImages: [AgentImageAttachment] = []
136146
var composerText = ""
137147
var isRunningStructuredOutputDemo = false
148+
var isRunningStructuredStreamingDemo = false
138149
var structuredShippingReplyResult: StructuredOutputDemoDraftResult?
139150
var structuredImportedSummaryResult: StructuredOutputDemoImportResult?
151+
var structuredStreamingResult: StructuredStreamingDemoResult?
152+
var structuredStreamingError: String?
140153
var isRunningMemoryDemo = false
141154
var automaticMemoryResult: AutomaticMemoryDemoResult?
142155
var automaticPolicyMemoryResult: AutomaticPolicyMemoryDemoResult?

DemoApp/AssistantRuntimeDemoApp/Shared/DemoStructuredOutputExamples.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ struct StructuredImportedContentSummary: AgentStructuredOutput, Sendable {
4747
)
4848
}
4949

50+
struct StreamedStructuredDeliveryUpdate: AgentStructuredOutput, Sendable, Hashable {
51+
let statusHeadline: String
52+
let customerPromise: String
53+
let nextAction: String
54+
55+
static let responseFormat = AgentStructuredOutputFormat(
56+
name: "streamed_delivery_update",
57+
description: "A structured operational delivery update produced alongside visible assistant narration.",
58+
schema: .object(
59+
properties: [
60+
"statusHeadline": .string(),
61+
"customerPromise": .string(),
62+
"nextAction": .string(),
63+
],
64+
required: ["statusHeadline", "customerPromise", "nextAction"],
65+
additionalProperties: false
66+
)
67+
)
68+
}
69+
5070
enum DemoStructuredOutputExamples {
5171
static let shippingCustomerMessage = """
5272
My package was supposed to arrive yesterday for a birthday on Saturday. Tracking has not moved in two days and I need to know whether it will make it in time.
@@ -57,6 +77,9 @@ enum DemoStructuredOutputExamples {
5777
"""
5878

5979
static let importedArticleURL = URL(string: "https://github.com/timazed/CodexKit")!
80+
static let streamedStructuredPrompt = """
81+
The package is delayed ahead of a birthday delivery. Talk to the customer like an in-app support assistant while you work through the situation. Stream a short human-readable response only. Do not restate the final structured delivery fields in prose because the app receives those separately. Then provide the final typed delivery update for the app.
82+
"""
6083

6184
static func shippingReplyRequest() -> UserMessageRequest {
6285
UserMessageRequest(
@@ -78,4 +101,8 @@ enum DemoStructuredOutputExamples {
78101
)
79102
)
80103
}
104+
105+
static func streamedStructuredRequest() -> UserMessageRequest {
106+
UserMessageRequest(text: streamedStructuredPrompt)
107+
}
81108
}

0 commit comments

Comments
 (0)