Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8268a7b
add vanilla agent dashboard
DidierRLopes Sep 15, 2025
9aa97db
list all widgets in dashboard
DidierRLopes Sep 15, 2025
efc8f1e
better
DidierRLopes Sep 15, 2025
e7f741c
better
DidierRLopes Sep 15, 2025
a843cc4
cleaned up version
DidierRLopes Sep 15, 2025
64b1df0
separate explicit from dashboard
DidierRLopes Sep 15, 2025
1161d81
nicer formatting
DidierRLopes Sep 15, 2025
d579304
clean up code
DidierRLopes Sep 15, 2025
14118fe
better
DidierRLopes Sep 15, 2025
fcd503d
better
DidierRLopes Sep 15, 2025
c21cca3
fix this folder
DidierRLopes Sep 15, 2025
06956c1
improve readme
DidierRLopes Sep 15, 2025
b01c3b9
lint
DidierRLopes Sep 15, 2025
9230218
fix desc
DidierRLopes Sep 15, 2025
0b7a3bc
clean up
DidierRLopes Sep 15, 2025
a485b5a
20 financial prompt optimizer
DidierRLopes Sep 16, 2025
6b46e09
remove extra yield from reasoning ex
DidierRLopes Sep 16, 2025
c289835
Secondary
DidierRLopes Sep 16, 2025
29dd790
40 vanilla agent dashboards widgets
DidierRLopes Sep 16, 2025
49eef84
cleaning
DidierRLopes Sep 16, 2025
29b9a18
ruff
DidierRLopes Sep 16, 2025
ca4a93c
ruff
DidierRLopes Sep 16, 2025
53de4c9
add vanilla pdf citations
DidierRLopes Sep 17, 2025
e15032f
rename
DidierRLopes Sep 17, 2025
69b2f43
Remove duplicate example entry in README
DidierRLopes Sep 17, 2025
c729438
Adjust image dimensions in README.md
DidierRLopes Sep 17, 2025
422810d
ruff
DidierRLopes Sep 17, 2025
16355d7
Update readme files
piiq Sep 18, 2025
44b7097
Remove __all__ from the 40 agent __init__
piiq Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions 20-financial-prompt-optimizer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Example agent for financial prompt optimization

This is a minimal example agent, powered by OpenAI, that optimizes a user's
financial prompt for clarity, specificity, and actionability. It has no widget
integration and focuses solely on improving prompts.

## Getting started

Here's how to get your agent up and running:

### Prerequisites

Ensure you have poetry, a tool for dependency management and packaging in
Python, as well as your OpenAI API key.

### Installation and Running

1. Clone this repository to your local machine.

2. Install the necessary dependencies:

```sh
poetry install --no-root
```

3. Set the OpenAI API key as an environment variable:

```sh
export OPENAI_API_KEY=<your-api-key>
```

4. Start the API server:

```sh
cd financial-prompt-optimizer
poetry run uvicorn main:app --port 7777 --reload
```

This command runs the FastAPI application, making it accessible on your network.

### Accessing the Documentation

Once the API server is running, you can view the documentation and interact with
the API by visiting: [http://localhost:7777/docs](http://localhost:7777/docs)

### Using with OpenBB Workspace (Optional)

- The agent descriptor is available at: [http://localhost:7777/agents.json](http://localhost:7777/agents.json)
- Features are set to:
- `streaming: true`
- `widget-dashboard-select: false`
- `widget-dashboard-search: false`

### Expected Behavior

When you send a user message, the agent streams a single answer containing two
sections:

- `Optimized Prompt: <single improved prompt>`
- `Rationale: <1–3 short bullets on what changed and why>`
100 changes: 100 additions & 0 deletions 20-financial-prompt-optimizer/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from typing import AsyncGenerator
import openai

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from sse_starlette.sse import EventSourceResponse

from openbb_ai.models import MessageChunkSSE, QueryRequest
from openbb_ai import message_chunk

from openai.types.chat import (
ChatCompletionMessageParam,
ChatCompletionUserMessageParam,
ChatCompletionAssistantMessageParam,
ChatCompletionSystemMessageParam,
)


app = FastAPI()

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


@app.get("/agents.json")
def get_copilot_description():
"""Agent descriptor for the OpenBB Workspace."""
return JSONResponse(
content={
"financial_prompt_optimizer": {
"name": "Financial Prompt Optimizer",
"description": "Optimizes a user's prompt for finance: clearer, more specific, and actionable.",
"image": "https://github.com/OpenBB-finance/copilot-for-terminal-pro/assets/14093308/7da2a512-93b9-478d-90bc-b8c3dd0cabcf",
"endpoints": {"query": "http://localhost:7777/v1/query"},
"features": {
"streaming": True,
"widget-dashboard-select": False,
"widget-dashboard-search": False,
},
}
}
)


@app.post("/v1/query")
async def query(request: QueryRequest) -> EventSourceResponse:
"""Stream a concise optimized prompt and rationale."""

openai_messages: list[ChatCompletionMessageParam] = [
ChatCompletionSystemMessageParam(
role="system",
content=(
"You are a concise Financial Prompt Optimizer.\n"
"Rewrite the user's prompt to be clearer, more specific, and immediately actionable for financial analysis.\n"
"Always return exactly the improved prompt:\n"
"Optimized Prompt: <detailed improved prompt with step-by-step>\n"
),
)
]

for message in request.messages:
if message.role == "human":
openai_messages.append(
ChatCompletionUserMessageParam(role="user", content=message.content)
)
elif message.role == "ai" and isinstance(message.content, str):
openai_messages.append(
ChatCompletionAssistantMessageParam(
role="assistant", content=message.content
)
)

async def execution_loop() -> AsyncGenerator[MessageChunkSSE, None]:
client = openai.AsyncOpenAI()
async for event in await client.chat.completions.create(
model="gpt-4o",
messages=openai_messages,
stream=True,
):
if chunk := event.choices[0].delta.content:
yield message_chunk(chunk)

return EventSourceResponse(
content=(
event.model_dump(exclude_none=True) async for event in execution_loop()
),
media_type="text/event-stream",
)


if __name__ == "__main__":
import uvicorn

uvicorn.run("main:app", host="0.0.0.0", port=7777, reload=True)
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ async def query(request: QueryRequest) -> EventSourceResponse:
and request.widgets.primary
):
widget_requests: list[WidgetRequest] = []
# Note: If we wanted to iterate through the widgets on the dashboard
# rather than on the widgets on the explicit context
# then we would need to iterate through request.widgets.secondary
# and the agents.json would need "widget-dashboard-search": True
for widget in request.widgets.primary:
widget_requests.append(
WidgetRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

app.add_middleware(
CORSMiddleware,
allow_origins=["https://pro.openbb.co"],
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
Expand Down Expand Up @@ -101,13 +101,6 @@ async def execution_loop() -> AsyncGenerator[MessageChunkSSE, None]:
if chunk := event.choices[0].delta.content:
yield message_chunk(chunk).model_dump()

# Reasoning steps can be yielded from anywhere, as long as they are
# yielded from the execution loop back to the OpenBB Workspace.
yield reasoning_step(
event_type="INFO",
message="Answering complete!",
).model_dump()

return EventSourceResponse(
content=execution_loop(),
media_type="text/event-stream",
Expand Down
57 changes: 57 additions & 0 deletions 36-vanilla-agent-pdf-citations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Example agent that can handle PDF data with citations

This is an example agent, powered by OpenAI, that can perform question answering
using data retrieved from widgets on OpenBB Workspace. It specifically demonstrates
how to handle PDF data with citation support, including highlighting specific text passages.

## Getting started

Here's how to get your agent up and running:

### Prerequisites

Ensure you have poetry, a tool for dependency management and packaging in
Python, as well as your OpenAI API key.

### Installation and Running

1. Clone this repository to your local machine.

2. Set the OpenAI API key as an environment variable in your .bashrc or .zshrc file:

``` sh
# in .zshrc or .bashrc
export OPENAI_API_KEY=<your-api-key>
```

3. Install the necessary dependencies:

``` sh
poetry install --no-root
```

4.Start the API server:

``` sh
cd 36-vanilla-pdf-citations
poetry run uvicorn vanilla_agent_pdf_citations.main:app --port 7777 --reload
```

This command runs the FastAPI application, making it accessible on your network.

### Testing the Agent

The example agent has a small, basic test suite to ensure it's
working correctly. As you develop your agent, you are highly encouraged to
expand these tests.

You can run the tests with:

```sh
pytest tests
```

### Accessing the Documentation

Once the API server is running, you can view the documentation and interact with
the API by visiting: http://localhost:7777/docs
Empty file.
85 changes: 85 additions & 0 deletions 36-vanilla-agent-pdf-citations/tests/test_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import json
from unittest import mock
from fastapi.testclient import TestClient
from vanilla_agent_pdf.main import app
import pytest
from pathlib import Path
from openbb_ai.testing import CopilotResponse

test_client = TestClient(app)


@pytest.fixture(autouse=True)
def reset_sse_starlette_appstatus_event():
"""
Fixture that resets the appstatus event in the sse_starlette app.
Should be used on any test that uses sse_starlette to stream events.
"""
# See https://github.com/sysid/sse-starlette/issues/59
from sse_starlette.sse import AppStatus

AppStatus.should_exit_event = None


def test_query():
test_payload_path = (
Path(__file__).parent.parent.parent
/ "testing"
/ "test_payloads"
/ "single_message.json"
)
test_payload = json.load(open(test_payload_path))

response = test_client.post("/v1/query", json=test_payload)
assert response.status_code == 200
copilot_response = CopilotResponse(response.text)
(copilot_response.has_any("copilotMessage", "2"))


def test_query_conversation():
test_payload_path = (
Path(__file__).parent.parent.parent
/ "testing"
/ "test_payloads"
/ "multiple_messages.json"
)
test_payload = json.load(open(test_payload_path))

response = test_client.post("/v1/query", json=test_payload)
assert response.status_code == 200
copilot_response = CopilotResponse(response.text)
(copilot_response.has_any("copilotMessage", "4"))


def test_query_no_messages():
test_payload = {
"messages": [],
}
response = test_client.post("/v1/query", json=test_payload)
"messages list cannot be empty" in response.text


def test_query_completes_remote_function_call_with_pdf_url():
test_payload_path = (
Path(__file__).parent.parent.parent
/ "testing"
/ "test_payloads"
/ "message_with_primary_widget_and_tool_call_pdf_url.json"
)
test_payload = json.load(open(test_payload_path))

with open(
Path(__file__).parent.parent.parent
/ "testing"
/ "test_payloads"
/ "openbb_story.pdf",
"rb",
) as pdf:
pdf_content = pdf.read()

with mock.patch("vanilla_agent_pdf.main._download_file", return_value=pdf_content):
response = test_client.post("/v1/query", json=test_payload)

assert response.status_code == 200
copilot_response = CopilotResponse(response.text)
(copilot_response.starts("copilotMessage").with_("Didier Lopes").with_("Gamestonk"))
Empty file.
Loading