ofermend commited on
Commit
2fb0169
1 Parent(s): f8d2846
Files changed (2) hide show
  1. agent.py +144 -0
  2. app.py +17 -154
agent.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import pandas as pd
4
+ import requests
5
+
6
+ from omegaconf import OmegaConf
7
+
8
+ from dotenv import load_dotenv
9
+ load_dotenv(override=True)
10
+
11
+ from pydantic import Field, BaseModel
12
+ from vectara_agent.agent import Agent, AgentStatusType
13
+ from vectara_agent.tools import ToolsFactory, VectaraToolFactory
14
+
15
+ tickers = {
16
+ "AAPL": "Apple Computer",
17
+ "GOOG": "Google",
18
+ "AMZN": "Amazon",
19
+ "SNOW": "Snowflake",
20
+ "TEAM": "Atlassian",
21
+ "TSLA": "Tesla",
22
+ "NVDA": "Nvidia",
23
+ "MSFT": "Microsoft",
24
+ "AMD": "Advanced Micro Devices",
25
+ "INTC": "Intel",
26
+ "NFLX": "Netflix",
27
+ }
28
+ years = [2020, 2021, 2022, 2023, 2024]
29
+ initial_prompt = "How can I help you today?"
30
+
31
+ def create_assistant_tools(cfg):
32
+
33
+ def get_company_info() -> list[str]:
34
+ """
35
+ Returns a dictionary of companies you can query about. Always check this before using any other tool.
36
+ The output is a dictionary of valid ticker symbols mapped to company names.
37
+ You can use this to identify the companies you can query about, and their ticker information.
38
+ """
39
+ return tickers
40
+
41
+ def get_valid_years() -> list[str]:
42
+ """
43
+ Returns a list of the years for which financial reports are available.
44
+ Always check this before using any other tool.
45
+ """
46
+ return years
47
+
48
+ # Tool to get the income statement for a given company and year using the FMP API
49
+ def get_income_statement(
50
+ ticker=Field(description="the ticker symbol of the company."),
51
+ year=Field(description="the year for which to get the income statement."),
52
+ ) -> str:
53
+ """
54
+ Get the income statement for a given company and year using the FMP (https://financialmodelingprep.com) API.
55
+ Returns a dictionary with the income statement data. All data is in USD, but you can convert it to more compact form like K, M, B.
56
+ """
57
+ fmp_api_key = os.environ.get("FMP_API_KEY", None)
58
+ if fmp_api_key is None:
59
+ return "FMP_API_KEY environment variable not set. This tool does not work."
60
+ url = f"https://financialmodelingprep.com/api/v3/income-statement/{ticker}?apikey={fmp_api_key}"
61
+ response = requests.get(url)
62
+ if response.status_code == 200:
63
+ data = response.json()
64
+ income_statement = pd.DataFrame(data)
65
+ income_statement["date"] = pd.to_datetime(income_statement["date"])
66
+ income_statement_specific_year = income_statement[
67
+ income_statement["date"].dt.year == int(year)
68
+ ]
69
+ values_dict = income_statement_specific_year.to_dict(orient="records")[0]
70
+ return f"Financial results: {', '.join([f'{key}: {value}' for key, value in values_dict.items() if key not in ['date', 'cik', 'link', 'finalLink']])}"
71
+ else:
72
+ return "FMP API returned error. This tool does not work."
73
+
74
+ class QueryTranscriptsArgs(BaseModel):
75
+ query: str = Field(..., description="The user query.")
76
+ year: int = Field(..., description=f"The year. An integer between {min(years)} and {max(years)}.")
77
+ ticker: str = Field(..., description=f"The company ticker. Must be a valid ticket symbol from the list {tickers.keys()}.")
78
+
79
+ vec_factory = VectaraToolFactory(vectara_api_key=cfg.api_key,
80
+ vectara_customer_id=cfg.customer_id,
81
+ vectara_corpus_id=cfg.corpus_id)
82
+ tools_factory = ToolsFactory()
83
+
84
+ ask_transcripts = vec_factory.create_rag_tool(
85
+ tool_name = "ask_transcripts",
86
+ tool_description = """
87
+ Given a company name and year, responds to a user question about the company, based on analyst call transcripts about the company's financial reports for that year.
88
+ You can ask this tool any question about the compaany including risks, opportunities, financial performance, competitors and more.
89
+ """,
90
+ tool_args_schema = QueryTranscriptsArgs,
91
+ reranker = "multilingual_reranker_v1", rerank_k = 100,
92
+ n_sentences_before = 2, n_sentences_after = 2, lambda_val = 0.005,
93
+ summary_num_results = 10,
94
+ vectara_summarizer = 'vectara-summary-ext-24-05-med-omni',
95
+ include_citations = False,
96
+ )
97
+
98
+ return (
99
+ [tools_factory.create_tool(tool) for tool in
100
+ [
101
+ get_company_info,
102
+ get_valid_years,
103
+ get_income_statement,
104
+ ]
105
+ ] +
106
+ tools_factory.standard_tools() +
107
+ tools_factory.financial_tools() +
108
+ tools_factory.guardrail_tools() +
109
+ [ask_transcripts]
110
+ )
111
+
112
+ def initialize_agent(_cfg, update_func):
113
+ financial_bot_instructions = """
114
+ - You are a helpful financial assistant, with expertise in financial reporting, in conversation with a user.
115
+ - Respond in a compact format by using appropriate units of measure (e.g., K for thousands, M for millions, B for billions).
116
+ Do not report the same number twice (e.g. $100K and 100,000 USD).
117
+ - Always check the get_company_info and get_valid_years tools to validate company and year are valid.
118
+ - Do not include URLS unless they are from one of the tools.
119
+ - When querying a tool for a numeric value or KPI, use a concise and non-ambiguous description of what you are looking for.
120
+ - If you calculate a metric, make sure you have all the necessary information to complete the calculation. Don't guess.
121
+ """
122
+
123
+ agent = Agent(
124
+ tools=create_assistant_tools(_cfg),
125
+ topic="Financial data, annual reports and 10-K filings",
126
+ custom_instructions=financial_bot_instructions,
127
+ update_func=update_func
128
+ )
129
+ agent.report()
130
+ return agent
131
+
132
+
133
+ def get_agent_config() -> OmegaConf:
134
+ companies = ", ".join(tickers.values())
135
+ cfg = OmegaConf.create({
136
+ 'customer_id': str(os.environ['VECTARA_CUSTOMER_ID']),
137
+ 'corpus_id': str(os.environ['VECTARA_CORPUS_ID']),
138
+ 'api_key': str(os.environ['VECTARA_API_KEY']),
139
+ 'examples': os.environ.get('QUERY_EXAMPLES', None),
140
+ 'title': "Financial Assistant",
141
+ 'demo_welcome': "Welcome to the Financial Assistant demo.",
142
+ 'demo_description': f"This assistant can help you with any questions about the financials of several companies:\n\n **{companies}**.\n"
143
+ })
144
+ return cfg
app.py CHANGED
@@ -1,148 +1,15 @@
1
-
2
- import os
3
  from PIL import Image
4
  import sys
5
- import pandas as pd
6
- import requests
7
 
8
- from omegaconf import OmegaConf
9
  import streamlit as st
10
  from streamlit_pills import pills
11
 
12
- from dotenv import load_dotenv
13
- load_dotenv(override=True)
14
 
15
- from pydantic import Field, BaseModel
16
- from vectara_agent.agent import Agent, AgentStatusType
17
- from vectara_agent.tools import ToolsFactory, VectaraToolFactory
18
 
19
- tickers = {
20
- "AAPL": "Apple Computer",
21
- "GOOG": "Google",
22
- "AMZN": "Amazon",
23
- "SNOW": "Snowflake",
24
- "TEAM": "Atlassian",
25
- "TSLA": "Tesla",
26
- "NVDA": "Nvidia",
27
- "MSFT": "Microsoft",
28
- "AMD": "Advanced Micro Devices",
29
- "INTC": "Intel",
30
- "NFLX": "Netflix",
31
- }
32
- years = [2020, 2021, 2022, 2023, 2024]
33
  initial_prompt = "How can I help you today?"
34
 
35
- def create_assistant_tools(cfg):
36
-
37
- def get_company_info() -> list[str]:
38
- """
39
- Returns a dictionary of companies you can query about. Always check this before using any other tool.
40
- The output is a dictionary of valid ticker symbols mapped to company names.
41
- You can use this to identify the companies you can query about, and their ticker information.
42
- """
43
- return tickers
44
-
45
- def get_valid_years() -> list[str]:
46
- """
47
- Returns a list of the years for which financial reports are available.
48
- Always check this before using any other tool.
49
- """
50
- return years
51
-
52
- # Tool to get the income statement for a given company and year using the FMP API
53
- def get_income_statement(
54
- ticker=Field(description="the ticker symbol of the company."),
55
- year=Field(description="the year for which to get the income statement."),
56
- ) -> str:
57
- """
58
- Get the income statement for a given company and year using the FMP (https://financialmodelingprep.com) API.
59
- Returns a dictionary with the income statement data. All data is in USD, but you can convert it to more compact form like K, M, B.
60
- """
61
- fmp_api_key = os.environ.get("FMP_API_KEY", None)
62
- if fmp_api_key is None:
63
- return "FMP_API_KEY environment variable not set. This tool does not work."
64
- url = f"https://financialmodelingprep.com/api/v3/income-statement/{ticker}?apikey={fmp_api_key}"
65
- response = requests.get(url)
66
- if response.status_code == 200:
67
- data = response.json()
68
- income_statement = pd.DataFrame(data)
69
- income_statement["date"] = pd.to_datetime(income_statement["date"])
70
- income_statement_specific_year = income_statement[
71
- income_statement["date"].dt.year == int(year)
72
- ]
73
- values_dict = income_statement_specific_year.to_dict(orient="records")[0]
74
- return f"Financial results: {', '.join([f'{key}: {value}' for key, value in values_dict.items() if key not in ['date', 'cik', 'link', 'finalLink']])}"
75
- else:
76
- return "FMP API returned error. This tool does not work."
77
-
78
- class QueryTranscriptsArgs(BaseModel):
79
- query: str = Field(..., description="The user query.")
80
- year: int = Field(..., description=f"The year. An integer between {min(years)} and {max(years)}.")
81
- ticker: str = Field(..., description=f"The company ticker. Must be a valid ticket symbol from the list {tickers.keys()}.")
82
-
83
- vec_factory = VectaraToolFactory(vectara_api_key=cfg.api_key,
84
- vectara_customer_id=cfg.customer_id,
85
- vectara_corpus_id=cfg.corpus_id)
86
- tools_factory = ToolsFactory()
87
-
88
- ask_transcripts = vec_factory.create_rag_tool(
89
- tool_name = "ask_transcripts",
90
- tool_description = """
91
- Given a company name and year, responds to a user question about the company, based on analyst call transcripts about the company's financial reports for that year.
92
- You can ask this tool any question about the compaany including risks, opportunities, financial performance, competitors and more.
93
- """,
94
- tool_args_schema = QueryTranscriptsArgs,
95
- reranker = "multilingual_reranker_v1", rerank_k = 100,
96
- n_sentences_before = 2, n_sentences_after = 2, lambda_val = 0.005,
97
- summary_num_results = 10,
98
- vectara_summarizer = 'vectara-summary-ext-24-05-med-omni',
99
- include_citations = False,
100
- )
101
-
102
- return (
103
- [tools_factory.create_tool(tool) for tool in
104
- [
105
- get_company_info,
106
- get_valid_years,
107
- get_income_statement,
108
- ]
109
- ] +
110
- tools_factory.standard_tools() +
111
- tools_factory.financial_tools() +
112
- tools_factory.guardrail_tools() +
113
- [ask_transcripts]
114
- )
115
-
116
- def initialize_agent(_cfg):
117
- if 'agent' in st.session_state:
118
- return st.session_state.agent
119
-
120
- financial_bot_instructions = """
121
- - You are a helpful financial assistant, with expertise in financial reporting, in conversation with a user.
122
- - Respond in a compact format by using appropriate units of measure (e.g., K for thousands, M for millions, B for billions).
123
- Do not report the same number twice (e.g. $100K and 100,000 USD).
124
- - Always check the get_company_info and get_valid_years tools to validate company and year are valid.
125
- - Do not include URLS unless they are from one of the tools.
126
- - When querying a tool for a numeric value or KPI, use a concise and non-ambiguous description of what you are looking for.
127
- - If you calculate a metric, make sure you have all the necessary information to complete the calculation. Don't guess.
128
- """
129
-
130
- def update_func(status_type: AgentStatusType, msg: str):
131
- if status_type != AgentStatusType.AGENT_UPDATE:
132
- output = f"{status_type.value} - {msg}"
133
- st.session_state.log_messages.append(output)
134
-
135
- agent = Agent(
136
- tools=create_assistant_tools(_cfg),
137
- topic="Financial data, annual reports and 10-K filings",
138
- custom_instructions=financial_bot_instructions,
139
- update_func=update_func
140
- )
141
- agent.report()
142
-
143
- return agent
144
-
145
-
146
  def toggle_logs():
147
  st.session_state.show_logs = not st.session_state.show_logs
148
 
@@ -155,6 +22,11 @@ def show_example_questions():
155
  return True
156
  return False
157
 
 
 
 
 
 
158
  def launch_bot():
159
  def reset():
160
  st.session_state.messages = [{"role": "assistant", "content": initial_prompt, "avatar": "🦖"}]
@@ -162,17 +34,13 @@ def launch_bot():
162
  st.session_state.log_messages = []
163
  st.session_state.prompt = None
164
  st.session_state.ex_prompt = None
165
- st.session_state.show_logs = False
166
  st.session_state.first_turn = True
 
 
 
167
 
168
- st.set_page_config(page_title="Financial Assistant", layout="wide")
169
  if 'cfg' not in st.session_state:
170
- cfg = OmegaConf.create({
171
- 'customer_id': str(os.environ['VECTARA_CUSTOMER_ID']),
172
- 'corpus_id': str(os.environ['VECTARA_CORPUS_ID']),
173
- 'api_key': str(os.environ['VECTARA_API_KEY']),
174
- 'examples': os.environ.get('QUERY_EXAMPLES', None)
175
- })
176
  st.session_state.cfg = cfg
177
  st.session_state.ex_prompt = None
178
  example_messages = [example.strip() for example in cfg.examples.split(",")] if cfg.examples else []
@@ -180,18 +48,14 @@ def launch_bot():
180
  reset()
181
 
182
  cfg = st.session_state.cfg
183
- if 'agent' not in st.session_state:
184
- st.session_state.agent = initialize_agent(cfg)
185
 
186
  # left side content
187
  with st.sidebar:
188
  image = Image.open('Vectara-logo.png')
189
  st.image(image, width=175)
190
- st.markdown("## Welcome to the financial assistant demo.\n\n\n")
191
- companies = ", ".join(tickers.values())
192
- st.markdown(
193
- f"This assistant can help you with any questions about the financials of several companies:\n\n **{companies}**.\n"
194
- )
195
 
196
  st.markdown("\n\n")
197
  bc1, _ = st.columns([1, 1])
@@ -206,7 +70,6 @@ def launch_bot():
206
  "This app was built with [Vectara](https://vectara.com).\n\n"
207
  "It demonstrates the use of Agentic RAG functionality with Vectara"
208
  )
209
- st.markdown("---")
210
 
211
  if "messages" not in st.session_state.keys():
212
  reset()
@@ -249,8 +112,9 @@ def launch_bot():
249
  st.markdown(res)
250
  st.session_state.ex_prompt = None
251
  st.session_state.prompt = None
 
252
  st.rerun()
253
-
254
  log_placeholder = st.empty()
255
  with log_placeholder.container():
256
  if st.session_state.show_logs:
@@ -264,5 +128,4 @@ def launch_bot():
264
  sys.stdout.flush()
265
 
266
  if __name__ == "__main__":
267
- launch_bot()
268
-
 
 
 
1
  from PIL import Image
2
  import sys
 
 
3
 
 
4
  import streamlit as st
5
  from streamlit_pills import pills
6
 
7
+ from vectara_agent.agent import AgentStatusType
 
8
 
9
+ from agent import initialize_agent, get_agent_config
 
 
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  initial_prompt = "How can I help you today?"
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  def toggle_logs():
14
  st.session_state.show_logs = not st.session_state.show_logs
15
 
 
22
  return True
23
  return False
24
 
25
+ def update_func(status_type: AgentStatusType, msg: str):
26
+ if status_type != AgentStatusType.AGENT_UPDATE:
27
+ output = f"{status_type.value} - {msg}"
28
+ st.session_state.log_messages.append(output)
29
+
30
  def launch_bot():
31
  def reset():
32
  st.session_state.messages = [{"role": "assistant", "content": initial_prompt, "avatar": "🦖"}]
 
34
  st.session_state.log_messages = []
35
  st.session_state.prompt = None
36
  st.session_state.ex_prompt = None
 
37
  st.session_state.first_turn = True
38
+ st.session_state.show_logs = False
39
+ if 'agent' not in st.session_state:
40
+ st.session_state.agent = initialize_agent(cfg, update_func=update_func)
41
 
 
42
  if 'cfg' not in st.session_state:
43
+ cfg = get_agent_config()
 
 
 
 
 
44
  st.session_state.cfg = cfg
45
  st.session_state.ex_prompt = None
46
  example_messages = [example.strip() for example in cfg.examples.split(",")] if cfg.examples else []
 
48
  reset()
49
 
50
  cfg = st.session_state.cfg
51
+ st.set_page_config(page_title=cfg['title'], layout="wide")
 
52
 
53
  # left side content
54
  with st.sidebar:
55
  image = Image.open('Vectara-logo.png')
56
  st.image(image, width=175)
57
+ st.markdown(f"## {cfg['demo_welcome']}")
58
+ st.markdown(f"{cfg['demo_description']}")
 
 
 
59
 
60
  st.markdown("\n\n")
61
  bc1, _ = st.columns([1, 1])
 
70
  "This app was built with [Vectara](https://vectara.com).\n\n"
71
  "It demonstrates the use of Agentic RAG functionality with Vectara"
72
  )
 
73
 
74
  if "messages" not in st.session_state.keys():
75
  reset()
 
112
  st.markdown(res)
113
  st.session_state.ex_prompt = None
114
  st.session_state.prompt = None
115
+ st.session_state.first_turn = False
116
  st.rerun()
117
+
118
  log_placeholder = st.empty()
119
  with log_placeholder.container():
120
  if st.session_state.show_logs:
 
128
  sys.stdout.flush()
129
 
130
  if __name__ == "__main__":
131
+ launch_bot()