99from sse_starlette .sse import EventSourceResponse
1010
1111from openbb_ai .models import (
12+ Citation ,
13+ CitationHighlightBoundingBox ,
14+ CitationCollectionSSE ,
1215 MessageChunkSSE ,
16+ FunctionCallSSE ,
1317 QueryRequest ,
1418 SingleFileReference ,
1519 SingleDataContent ,
1822 DataFileReferences ,
1923 WidgetRequest ,
2024)
21- from openbb_ai import message_chunk , get_widget_data
25+ from openbb_ai import message_chunk , get_widget_data , citations , cite
2226
2327from openai .types .chat import (
2428 ChatCompletionMessageParam ,
3842
3943app .add_middleware (
4044 CORSMiddleware ,
41- allow_origins = ["https ://pro.openbb.co " ],
45+ allow_origins = ["http ://localhost:1420 " ],
4246 allow_credentials = True ,
4347 allow_methods = ["*" ],
4448 allow_headers = ["*" ],
@@ -87,12 +91,12 @@ async def query(request: QueryRequest) -> EventSourceResponse:
8791 )
8892 )
8993
90- async def retrieve_widget_data ():
91- yield get_widget_data (widget_requests ). model_dump ()
94+ async def retrieve_widget_data () -> AsyncGenerator [ FunctionCallSSE , None ] :
95+ yield get_widget_data (widget_requests )
9296
9397 # Early exit to retrieve widget data
9498 return EventSourceResponse (
95- content = retrieve_widget_data (),
99+ content = ( event . model_dump () async for event in retrieve_widget_data () ),
96100 media_type = "text/event-stream" ,
97101 )
98102
@@ -105,6 +109,7 @@ async def retrieve_widget_data():
105109 ]
106110
107111 context_str = ""
112+ citations_list : list [Citation ] = []
108113 for index , message in enumerate (request .messages ):
109114 if message .role == "human" :
110115 openai_messages .append (
@@ -124,23 +129,83 @@ async def retrieve_widget_data():
124129 elif message .role == "tool" and index == len (request .messages ) - 1 :
125130 context_str += await handle_widget_data (message .data )
126131
132+ # We also need to create citations for the widget data we retrieved.
133+ for widget_data_request in message .input_arguments ["data_sources" ]:
134+ filtered_widgets = list (
135+ filter (
136+ lambda w : str (w .uuid ) == widget_data_request ["widget_uuid" ],
137+ request .widgets .primary ,
138+ )
139+ )
140+ if filtered_widgets :
141+ quote_bounding_boxes = [
142+ [
143+ CitationHighlightBoundingBox (
144+ text = "Some text chunk." ,
145+ page = 1 ,
146+ x0 = 72.0 ,
147+ top = 117 ,
148+ x1 = 259 ,
149+ bottom = 135 ,
150+ ),
151+ CitationHighlightBoundingBox (
152+ text = "Some text chunk." ,
153+ page = 1 ,
154+ x0 = 110.0 ,
155+ top = 140 ,
156+ x1 = 259 ,
157+ bottom = 160 ,
158+ ),
159+ ],
160+ [
161+ CitationHighlightBoundingBox (
162+ text = "Some text chunk." ,
163+ page = 1 ,
164+ x0 = 110 ,
165+ top = 170 ,
166+ x1 = 275 ,
167+ bottom = 185 ,
168+ ),
169+ ],
170+ ]
171+ citation = cite (
172+ widget = filtered_widgets [0 ],
173+ input_arguments = widget_data_request ["input_args" ],
174+ # You can add any extra details you want to the
175+ # citation using the `extra_details` argument.
176+ extra_details = {
177+ "Widget Name" : filtered_widgets [0 ].name ,
178+ "Widget Input Arguments" : widget_data_request ["input_args" ],
179+ },
180+ )
181+ # Add the bounding boxes to the citation.
182+ # This is just an example, you can modify the bounding boxes
183+ # as needed.
184+ citation .quote_bounding_boxes = quote_bounding_boxes
185+ citations_list .append (citation )
186+
127187 if context_str :
128188 openai_messages [- 1 ]["content" ] += "\n \n " + context_str # type: ignore
129189
130190 # Define the execution loop.
131- async def execution_loop () -> AsyncGenerator [MessageChunkSSE , None ]:
191+ async def execution_loop () -> (
192+ AsyncGenerator [MessageChunkSSE | CitationCollectionSSE , None ]
193+ ):
132194 client = openai .AsyncOpenAI ()
133195 async for event in await client .chat .completions .create (
134196 model = "gpt-4o" ,
135197 messages = openai_messages ,
136198 stream = True ,
137199 ):
138200 if chunk := event .choices [0 ].delta .content :
139- yield message_chunk (chunk ).model_dump ()
201+ yield message_chunk (chunk )
202+
203+ if citations_list :
204+ yield citations (citations_list )
140205
141206 # Stream the SSEs back to the client.
142207 return EventSourceResponse (
143- content = execution_loop (),
208+ content = ( event . model_dump () async for event in execution_loop () ),
144209 media_type = "text/event-stream" ,
145210 )
146211
0 commit comments