|
| 1 | +from typing import AsyncGenerator |
| 2 | +import openai |
| 3 | + |
| 4 | +from fastapi import FastAPI |
| 5 | +from fastapi.middleware.cors import CORSMiddleware |
| 6 | +from fastapi.responses import JSONResponse |
| 7 | +from sse_starlette.sse import EventSourceResponse |
| 8 | + |
| 9 | +from openbb_ai.models import MessageChunkSSE, QueryRequest |
| 10 | +from openbb_ai import message_chunk |
| 11 | + |
| 12 | +from openai.types.chat import ( |
| 13 | + ChatCompletionMessageParam, |
| 14 | + ChatCompletionUserMessageParam, |
| 15 | + ChatCompletionAssistantMessageParam, |
| 16 | + ChatCompletionSystemMessageParam, |
| 17 | +) |
| 18 | + |
| 19 | + |
| 20 | +app = FastAPI() |
| 21 | + |
| 22 | +app.add_middleware( |
| 23 | + CORSMiddleware, |
| 24 | + allow_origins=["*"], |
| 25 | + allow_credentials=True, |
| 26 | + allow_methods=["*"], |
| 27 | + allow_headers=["*"], |
| 28 | +) |
| 29 | + |
| 30 | + |
| 31 | +@app.get("/agents.json") |
| 32 | +def get_copilot_description(): |
| 33 | + """Agent descriptor for the OpenBB Workspace.""" |
| 34 | + return JSONResponse( |
| 35 | + content={ |
| 36 | + "financial_prompt_optimizer": { |
| 37 | + "name": "Financial Prompt Optimizer", |
| 38 | + "description": "Optimizes a user's prompt for finance: clearer, more specific, and actionable.", |
| 39 | + "image": "https://github.com/OpenBB-finance/copilot-for-terminal-pro/assets/14093308/7da2a512-93b9-478d-90bc-b8c3dd0cabcf", |
| 40 | + "endpoints": {"query": "http://localhost:7777/v1/query"}, |
| 41 | + "features": { |
| 42 | + "streaming": True, |
| 43 | + "widget-dashboard-select": False, |
| 44 | + "widget-dashboard-search": False, |
| 45 | + }, |
| 46 | + } |
| 47 | + } |
| 48 | + ) |
| 49 | + |
| 50 | + |
| 51 | +@app.post("/v1/query") |
| 52 | +async def query(request: QueryRequest) -> EventSourceResponse: |
| 53 | + """Stream a concise optimized prompt and rationale.""" |
| 54 | + |
| 55 | + openai_messages: list[ChatCompletionMessageParam] = [ |
| 56 | + ChatCompletionSystemMessageParam( |
| 57 | + role="system", |
| 58 | + content=( |
| 59 | + "You are a concise Financial Prompt Optimizer.\n" |
| 60 | + "Rewrite the user's prompt to be clearer, more specific, and immediately actionable for financial analysis.\n" |
| 61 | + "Always return exactly the improved prompt:\n" |
| 62 | + "Optimized Prompt: <detailed improved prompt with step-by-step>\n" |
| 63 | + ), |
| 64 | + ) |
| 65 | + ] |
| 66 | + |
| 67 | + for message in request.messages: |
| 68 | + if message.role == "human": |
| 69 | + openai_messages.append( |
| 70 | + ChatCompletionUserMessageParam(role="user", content=message.content) |
| 71 | + ) |
| 72 | + elif message.role == "ai" and isinstance(message.content, str): |
| 73 | + openai_messages.append( |
| 74 | + ChatCompletionAssistantMessageParam( |
| 75 | + role="assistant", content=message.content |
| 76 | + ) |
| 77 | + ) |
| 78 | + |
| 79 | + async def execution_loop() -> AsyncGenerator[MessageChunkSSE, None]: |
| 80 | + client = openai.AsyncOpenAI() |
| 81 | + async for event in await client.chat.completions.create( |
| 82 | + model="gpt-4o", |
| 83 | + messages=openai_messages, |
| 84 | + stream=True, |
| 85 | + ): |
| 86 | + if chunk := event.choices[0].delta.content: |
| 87 | + yield message_chunk(chunk) |
| 88 | + |
| 89 | + return EventSourceResponse( |
| 90 | + content=( |
| 91 | + event.model_dump(exclude_none=True) async for event in execution_loop() |
| 92 | + ), |
| 93 | + media_type="text/event-stream", |
| 94 | + ) |
| 95 | + |
| 96 | + |
| 97 | +if __name__ == "__main__": |
| 98 | + import uvicorn |
| 99 | + |
| 100 | + uvicorn.run("main:app", host="0.0.0.0", port=7777, reload=True) |
0 commit comments