返回 presentation-ai
route.ts
根目录 / src / app / api / agent / presentation / route.ts
1 import { toBaseMessages, toUIMessageStream } from "@ai-sdk/langchain";
2 import { type HumanMessage } from "@langchain/core/messages";
3 import { Command } from "@langchain/langgraph";
4 import {
5 consumeStream,
6 createUIMessageStreamResponse,
7 type UIMessage,
8 } from "ai";
9
10 import { createPresentationGraph } from "@/ai/agents/presentation/createAgent";
11 import { getLatestUserMessage } from "@/lib/ai/uiMessageParts";
12 import { logger } from "@/lib/observability/server/logger";
13 import { auth } from "@/server/auth";
14
15 type PresentationStreamOptions = Parameters<
16 ReturnType<typeof createPresentationGraph>["stream"]
17 >[1] & {
18 interruptBefore?: string[];
19 };
20
21 export async function POST(req: Request) {
22 let endSpanOnReturn = true;
23 const actionName = "agent.presentation.post";
24 const span = logger.startSpan(`allweone.api.${actionName}`, {
25 attributes: {
26 "allweone.scope": "api",
27 "allweone.action.type": "api_route",
28 "allweone.action.name": actionName,
29 "http.method": "POST",
30 "http.route": "/api/agent/presentation",
31 },
32 });
33
34 try {
35 const { id, messages, resumeData } = (await req.json()) as {
36 id?: string;
37 messages?: UIMessage[];
38 resumeData?: Record<string, unknown>;
39 };
40
41 if (!id) {
42 span.event("allweone.api.request_rejected", {
43 "allweone.validation.error": "missing_presentation_id",
44 });
45 return new Response("Missing presentation id", { status: 400 });
46 }
47
48 const session = await auth();
49
50 if (!session?.user) {
51 span.event("allweone.api.request_rejected", {
52 "allweone.validation.error": "unauthorized",
53 });
54 return new Response("Unauthorized", { status: 401 });
55 }
56
57 span.annotate({
58 "allweone.thread.id": `presentation:${id}`,
59 "allweone.thread.type": "presentation",
60 "allweone.presentation.thread_id": id,
61 "allweone.presentation.message.count": Array.isArray(messages)
62 ? messages.length
63 : 0,
64 "allweone.presentation.resume.present": Boolean(resumeData),
65 });
66
67 const graph = createPresentationGraph();
68 const streamOptions: PresentationStreamOptions = {
69 streamMode: ["values", "messages"],
70 interruptBefore: ["tools"],
71 configurable: {
72 thread_id: id,
73 },
74 };
75 const stream = await (resumeData
76 ? graph.stream(
77 new Command({ resume: resumeData }),
78 streamOptions as Parameters<typeof graph.stream>[1],
79 )
80 : (async () => {
81 const latestUserMessage = getLatestUserMessage(
82 Array.isArray(messages) ? messages : [],
83 );
84
85 const [lastUserMessage] = latestUserMessage
86 ? await toBaseMessages([latestUserMessage])
87 : [];
88
89 if (!lastUserMessage) {
90 throw new Error("No user message found in request");
91 }
92
93 return graph.stream(
94 {
95 messages: [lastUserMessage as HumanMessage],
96 },
97 streamOptions as Parameters<typeof graph.stream>[1],
98 );
99 })());
100 span.event("allweone.api.response_stream_created");
101 endSpanOnReturn = false;
102
103 return createUIMessageStreamResponse({
104 stream: toUIMessageStream(stream),
105 consumeSseStream: ({ stream: sseStream }) => {
106 void consumeStream({
107 stream: sseStream,
108 onError: (error) => {
109 span.error(error);
110 },
111 }).finally(() => {
112 span.end();
113 });
114 },
115 });
116 } catch (error) {
117 span.error(error);
118 return new Response("Internal Server Error", { status: 500 });
119 } finally {
120 if (endSpanOnReturn) {
121 span.end();
122 }
123 }
124 }
125
125 lines TYPESCRIPT