Skip to content

Commit f288395

Browse files
yanomalysamjulien
andcommitted
Tool calling apps (#8)
Co-authored-by: Sam Julien <sam.julien@writer.com>
1 parent 14bb789 commit f288395

31 files changed

Lines changed: 1068 additions & 11 deletions

finance-dashboard/charts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ def update_scatter_chart(state):
7272
)
7373

7474
# Set axis titles
75-
fig.update_yaxes(title_text=f"{state["symbol"]} Stock Price", secondary_y=False)
75+
fig.update_yaxes(title_text=f"{state['symbol']} Stock Price", secondary_y=False)
7676
fig.update_yaxes(title_text="S&P 500", secondary_y=True)
7777

7878
# Update layout
79-
fig.update_layout(height=550, title_text=f"{state["symbol"]} Stock vs the S&P 500", title_x = 0.5, title_y = 0.9, legend=dict(
79+
fig.update_layout(height=550, title_text=f"{state['symbol']} Stock vs the S&P 500", title_x = 0.5, title_y = 0.9, legend=dict(
8080
orientation='h',
8181
yanchor='top',
8282
y=-0.2, # Adjust this value as needed

finance-dashboard/main.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pandas as pd
44
from prompts import stock_prompts, income_prompts, earnings_prompt
55
from stock_data import download_data, download_sp500, stock_news, _one_day_data, income_statement, earnings_calls
6-
from charts import update_scatter_chart
6+
from charts import update_scatter_chart, handle_click
77
from dotenv import load_dotenv
88
import os
99

@@ -34,12 +34,12 @@ def _refresh_window(state):
3434
# Summarize earnings call using Palmyra-Fin model
3535
def summarize_earnings(state):
3636
_refresh_window(state)
37-
state["message"] = f"% {state["symbol"]} earnings call will be summarized here"
37+
state["message"] = f"% {state['symbol']} earnings call will be summarized here"
3838

3939
earnings_transcript = state["earnings_transcript"]
4040
prompt = earnings_prompt.format(earnings_transcript=earnings_transcript)
4141
submission = writer.ai.complete(prompt, config={"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192})
42-
state["message"] = f"+ {state["symbol"]} earnings call summary"
42+
state["message"] = f"+ {state['symbol']} earnings call summary"
4343
state["analysis"] = submission.strip()
4444
state["show_analysis_text"]["visible"] = True
4545

@@ -53,10 +53,10 @@ def prompt_parameters_lang(state,payload):
5353

5454
def generate_stock_analysis(state):
5555
_refresh_window(state)
56-
if(state["prompt_parameters_lang"] == ""):
56+
if state["prompt_parameters_lang"] == "":
5757
state["prompt_parameters_lang"] == "English"
5858

59-
state["message"] = f"% {state["symbol"]} trends will be analyzed here in {state['prompt_parameters_lang']}"
59+
state["message"] = f"% {state['symbol']} trends will be analyzed here in {state['prompt_parameters_lang']}"
6060
stock_name = state["symbol"]
6161
stock_data = state["main_df"][:365]
6262

@@ -68,7 +68,7 @@ def generate_stock_analysis(state):
6868
prompt = stock_prompts.format(language=language, stock_name=stock_name,words=integer_value,stock_data=stock_data)
6969
submission = writer.ai.complete(prompt, config={"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192})
7070
state["analysis"] = submission.strip()
71-
state["message"] = f"+ {state["symbol"]} trends analyzed"
71+
state["message"] = f"+ {state['symbol']} trends analyzed"
7272
state["show_analysis_text"]["visible"] = True
7373
state["show_analysis_text"]["language"] = True
7474

@@ -77,7 +77,7 @@ def generate_stock_analysis(state):
7777

7878
def generate_income_analysis(state):
7979
_refresh_window(state)
80-
state["message"] = f"% {state["symbol"]} income statement will be visualized here"
80+
state["message"] = f"% {state['symbol']} income statement will be visualized here"
8181
stock_name = state["symbol"]
8282
stock_data = state["main_df"][:365]
8383
income_statement_data = state["income_statement_df"][:365]
@@ -88,7 +88,7 @@ def generate_income_analysis(state):
8888
)
8989
submission = writer.ai.complete(prompt, config={"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192})
9090
state["analysis"] = submission.strip()
91-
state["message"] = f"+ {state["symbol"]} income statement visualized"
91+
state["message"] = f"+ {state['symbol']} income statement visualized"
9292
state["show_income_metrics"]["visible"] = True
9393
state["show_analysis_text"]["visible"] = True
9494
state["show_bar_graph"]["visible"] = True
@@ -107,7 +107,6 @@ def _get_main_df(filename):
107107
"last_24_hours_open": "168.76",
108108
"last_24_hours_high": "169.72",
109109
"last_24_hours_low": "167.50",
110-
"message": None,
111110
"main_df": _get_main_df("daily_IBM.csv"),
112111
"main_df_subset": _get_main_df("daily_IBM.csv"),
113112
"symbol": "AAPL",

financial-tools-chat/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
WRITER_API_KEY=your_writer_api_key
2+
EARNINGS_ANALYSIS_APP_ID=your_zero_code_app_id
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{"id": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "type": "page", "content": {"pageMode": "compact"}, "handlers": {}, "isCodeManaged": false, "parentId": "root", "position": 0}
2+
{"id": "4kukoixkdb66ixah", "type": "header", "content": {"text": "Financial tools chat"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 0}
3+
{"id": "kldz702eixpb8xnq", "type": "columns", "content": {"cssClasses": "column-container"}, "handlers": {}, "isCodeManaged": false, "parentId": "c0f99a9e-5004-4e75-a6c6-36f17490b134", "position": 1}
4+
{"id": "4nklk9e1wqetdhln", "type": "column", "content": {"width": "45"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 0}
5+
{"id": "pi01zfrzcoxpiqxu", "type": "chatbot", "content": {"avatarBackgroundColor": "#000000", "buttonColor": "#000000", "conversation": "@{conversation}", "cssClasses": "chat", "useMarkdown": "yes"}, "handlers": {"wf-chatbot-message": "message_handler"}, "isCodeManaged": false, "parentId": "4nklk9e1wqetdhln", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
6+
{"id": "52rbj5jd79undjij", "type": "column", "content": {"width": "50"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 1, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": false}}
7+
{"id": "e8iafahde157xsgr", "type": "tabs", "content": {"cssClasses": "visualization"}, "handlers": {}, "isCodeManaged": false, "parentId": "52rbj5jd79undjij", "position": 0}
8+
{"id": "stock", "type": "tab", "content": {"cssClasses": "", "name": "Stock"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 0, "visible": {"binding": "", "expression": "custom", "reversed": false}}
9+
{"id": "er0t7muikaatylvt", "type": "plotlygraph", "content": {"cssClasses": "stock", "spec": "@{stock_chart}"}, "handlers": {}, "isCodeManaged": false, "parentId": "stock", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
10+
{"id": "4n2cykskypjd6qo4", "type": "text", "content": {"text": "@{stock_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "stock", "position": 1}
11+
{"id": "income", "type": "tab", "content": {"cssClasses": "", "name": "Income"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 1, "visible": {"binding": "", "expression": "custom", "reversed": false}}
12+
{"id": "rqbqheyr0o5hs4fj", "type": "plotlygraph", "content": {"cssClasses": "income", "spec": "@{income_chart}"}, "handlers": {}, "isCodeManaged": false, "parentId": "income", "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
13+
{"id": "ssw8zcojyuup2aqn", "type": "text", "content": {"text": "@{income_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "income", "position": 1, "visible": {"binding": "", "expression": "custom", "reversed": false}}
14+
{"id": "earnings", "type": "tab", "content": {"cssClasses": "", "name": "Earnings"}, "handlers": {}, "isCodeManaged": false, "parentId": "e8iafahde157xsgr", "position": 2, "visible": {"binding": "", "expression": "custom", "reversed": false}}
15+
{"id": "3um69zhvgxb6efbh", "type": "text", "content": {"text": "@{earnings_analysis}", "useMarkdown": "yes"}, "handlers": {}, "isCodeManaged": false, "parentId": "earnings", "position": 0}
16+
{"id": "qwoll83rzy43r3rr", "type": "column", "content": {"cssClasses": "buttons", "width": "1"}, "handlers": {}, "isCodeManaged": false, "parentId": "kldz702eixpb8xnq", "position": 2, "visible": {"binding": "", "expression": "custom", "reversed": false}}
17+
{"id": "19f02wo3koxaipwp", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#000000", "cssClasses": "", "icon": "arrow_back_ios", "text": ""}, "handlers": {"wf-click": "open_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 0, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": true}}
18+
{"id": "72yjtz3kkrb5zr16", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#241f31", "cssClasses": "", "icon": "arrow_forward_ios", "text": ""}, "handlers": {"wf-click": "close_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 1, "visible": {"binding": "visual_block_visible", "expression": "custom", "reversed": false}}
19+
{"id": "kphyjxow4ppazmw1", "type": "button", "content": {"buttonColor": "#ffffff", "buttonTextColor": "#000000", "icon": "delete", "text": ""}, "handlers": {"wf-click": "clear_visualization"}, "isCodeManaged": false, "parentId": "qwoll83rzy43r3rr", "position": 2}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"id": "root", "type": "root", "content": {"appName": "Financial tools chat"}, "handlers": {}, "isCodeManaged": false, "position": 0}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"id": "workflows_root", "type": "workflows_root", "content": {}, "handlers": {}, "isCodeManaged": false, "position": 0, "visible": {"binding": "", "expression": true, "reversed": false}}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"writer_version": "0.8.1"
3+
}

financial-tools-chat/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This app was created using Writer Framework.
2+
3+
To learn more about it, visit https://dev.writer.com/framework

financial-tools-chat/chat_tools.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import json
2+
import os
3+
from io import StringIO
4+
5+
import pandas as pd
6+
import plotly.express as px
7+
import plotly.graph_objects as go
8+
import yfinance as yf
9+
from dotenv import load_dotenv
10+
from plotly.subplots import make_subplots
11+
from prompts import income_prompt, stock_prompt
12+
from writer.ai import apps, complete
13+
14+
load_dotenv()
15+
16+
17+
def _get_stock_analysis(symbol: str) -> str:
18+
config = {"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192}
19+
data = _get_stock_data(symbol)
20+
prompt = stock_prompt.format(name=symbol, data=data)
21+
stock_analysis = complete(prompt, config=config)
22+
return stock_analysis
23+
24+
25+
def _get_stock_data(symbol: str) -> str:
26+
df = yf.download(symbol, period="5y", interval="5d")
27+
df = df.reset_index()
28+
df = df.sort_values(by="Date", ascending=False)
29+
df = df.round({"Open": 2, "High": 2, "Low": 2, "Close": 2, "Adj Close": 2})
30+
df["Date"] = pd.to_datetime(df["Date"])
31+
return df.to_csv(index=False)
32+
33+
34+
def _get_income_analysis(symbol: str) -> str:
35+
config = {"model": "palmyra-fin-32k", "temperature": 0.7, "max_tokens": 8192}
36+
data = _get_income_data(symbol)
37+
prompt = income_prompt.format(name=symbol, data=data)
38+
income_analysis = complete(prompt, config=config)
39+
return income_analysis
40+
41+
42+
def _get_income_data(symbol: str) -> str:
43+
quarterly_income_stmt = yf.Ticker(symbol).quarterly_income_stmt
44+
df = pd.DataFrame(quarterly_income_stmt)
45+
df.columns = pd.to_datetime(df.columns).strftime("%Y-%m-%d")
46+
return df.to_csv()
47+
48+
49+
def _get_earnings_analysis(symbol: str) -> str:
50+
data = _get_earnings_data(symbol)
51+
earnings_analysis = apps.generate_content(
52+
application_id=os.getenv("EARNINGS_ANALYSIS_APP_ID", ""),
53+
input_dict={
54+
"name": symbol,
55+
"data": data,
56+
},
57+
)
58+
return earnings_analysis
59+
60+
61+
def _get_earnings_data(symbol: str) -> str:
62+
with open("earnings-data.json", "r") as file:
63+
earnings_transcript = json.load(file)
64+
if earnings_transcript:
65+
for item in earnings_transcript:
66+
if item["symbol"] == symbol:
67+
return item["content"]
68+
else:
69+
return "No earnings transcript found."
70+
71+
72+
def get_stock_chart(symbol: str) -> str:
73+
symbol_io = StringIO(_get_stock_data(symbol))
74+
sp500_io = StringIO(_get_stock_data("^GSPC"))
75+
76+
symbol_df = pd.read_csv(symbol_io)
77+
sp500_df = pd.read_csv(sp500_io)
78+
79+
fig = make_subplots(specs=[[{"secondary_y": True}]])
80+
81+
fig.add_trace(
82+
go.Scatter(x=symbol_df["Date"], y=symbol_df["Open"], name=symbol, mode="lines"),
83+
secondary_y=False,
84+
)
85+
86+
fig.add_trace(
87+
go.Scatter(
88+
x=sp500_df["Date"], y=sp500_df["Open"], name="S&P 500", mode="lines"
89+
),
90+
secondary_y=True,
91+
)
92+
93+
fig.update_yaxes(title_text=f"{symbol} Stock Price", secondary_y=False)
94+
fig.update_yaxes(title_text="S&P 500", secondary_y=True)
95+
96+
fig.update_layout(
97+
height=450,
98+
title_text=f"{symbol} Stock vs the S&P 500",
99+
title_x=0.5,
100+
title_y=0.9,
101+
legend=dict(
102+
orientation="h",
103+
yanchor="top",
104+
y=-0.2,
105+
xanchor="center",
106+
x=0.5,
107+
),
108+
)
109+
110+
return fig.to_json()
111+
112+
113+
def get_income_chart(symbol: str) -> str:
114+
string_io = StringIO(_get_income_data(symbol))
115+
116+
df = pd.read_csv(string_io, index_col=0)
117+
df_filtered = df.loc[["Total Revenue", "Net Income", "Operating Income"]]
118+
119+
df_transposed = df_filtered.transpose().reset_index()
120+
df_transposed = df_transposed.melt(
121+
id_vars=["index"], var_name="Metric", value_name="Value"
122+
)
123+
124+
fig = px.bar(
125+
df_transposed,
126+
x="index",
127+
y="Value",
128+
color="Metric",
129+
barmode="group",
130+
labels={"index": "", "Value": ""},
131+
title=f"Summary of Quarterly Income Statement for {symbol}",
132+
)
133+
134+
fig.update_layout(
135+
height=400,
136+
legend=dict(orientation="h", yanchor="top", y=-0.2, xanchor="center", x=0.5),
137+
)
138+
139+
return fig.to_json()
140+
141+
142+
stock_analysis_tool = {
143+
"type": "function",
144+
"name": "get_stock_analysis",
145+
"callable": _get_stock_analysis,
146+
"parameters": {
147+
"symbol": {
148+
"type": "string",
149+
"description": "Symbol to compose stock data analysis.",
150+
},
151+
},
152+
}
153+
154+
155+
income_analysis_tool = {
156+
"type": "function",
157+
"name": "get_income_analysis",
158+
"callable": _get_income_analysis,
159+
"parameters": {
160+
"symbol": {
161+
"type": "string",
162+
"description": "Symbol to compose income statement analysis.",
163+
},
164+
},
165+
}
166+
167+
168+
earnings_analysis_tool = {
169+
"type": "function",
170+
"name": "get_earnings_analysis",
171+
"callable": _get_earnings_analysis,
172+
"parameters": {
173+
"symbol": {
174+
"type": "string",
175+
"description": "Symbol to fetch earnings data and compose analysis.",
176+
},
177+
},
178+
}
179+
180+
181+
tools = [
182+
stock_analysis_tool,
183+
income_analysis_tool,
184+
earnings_analysis_tool,
185+
]

financial-tools-chat/earnings-data.json

Lines changed: 37 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)