Aleksey Matsarski commited on
Commit
07097fe
·
1 Parent(s): 2ef7125

clean code

Browse files
agents/__init__.py DELETED
File without changes
agents/agent_builder.py DELETED
@@ -1,62 +0,0 @@
1
- from langchain.agents import AgentExecutor, create_tool_calling_agent
2
- from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
3
- from tools.tools import search_news_tool, fetch_earnings_tool, fetch_market_snapshot_tool
4
-
5
- def build_agent(llm, tools, system_instructions: str) -> AgentExecutor:
6
- """Create a tool-calling agent with a focused system prompt."""
7
- prompt = ChatPromptTemplate.from_messages(
8
- [
9
- ("system", system_instructions),
10
- # MessagesPlaceholder("chat_history"),
11
- ("human", "{input}"),
12
- MessagesPlaceholder("agent_scratchpad"),
13
- ]
14
- )
15
- agent = create_tool_calling_agent(llm=llm, tools=tools, prompt=prompt)
16
- return AgentExecutor(agent=agent, tools=tools, verbose=False, handle_parsing_errors=True)
17
-
18
-
19
- def make_news_agent(model) -> AgentExecutor:
20
- sys = (
21
- "You are a News Analyst. Use the search tool to gather 5-8 recent, credible items. "
22
- "Synthesize themes, risks, catalysts, and sentiment for investors. Output a concise "
23
- "markdown summary with bullet points and 1-2 short citations (URLs)."
24
- )
25
- return build_agent(model, [search_news_tool], sys)
26
-
27
-
28
- def make_earnings_agent(model) -> AgentExecutor:
29
- sys = (
30
- "You are an Earnings Analyst. Use the earnings tool to summarize the latest and upcoming "
31
- "earnings information (dates, surprises if available) and key line items. Provide a "
32
- "short view on momentum and watchouts. Output concise markdown."
33
- )
34
- return build_agent(model, [fetch_earnings_tool], sys)
35
-
36
-
37
- def make_market_agent(model) -> AgentExecutor:
38
- sys = (
39
- "You are a Market & Valuation Analyst. Use the market snapshot tool to extract current "
40
- "trading context and discuss short-term technicals/flow and high-level valuation notes. "
41
- "Output concise markdown."
42
- )
43
- return build_agent(model, [fetch_market_snapshot_tool], sys)
44
-
45
-
46
- def make_synthesizer(model):
47
- """Final writer to merge all agent outputs into actionable recommendations."""
48
- template = ChatPromptTemplate.from_messages(
49
- [
50
- ("system",
51
- "You are the Lead Portfolio Analyst. Merge inputs from News, Earnings, and Market agents. "
52
- "Produce a final, actionable recommendation block (Buy/Hold/Sell with confidence 0-1), "
53
- "key drivers (bull/bear), near-term catalysts, and 2-3 risks. Be concise and concrete."),
54
- ("human",
55
- "Ticker: {ticker}\n\n"
56
- "### News Summary\n{news_summary}\n\n"
57
- "### Earnings Summary\n{earnings_summary}\n\n"
58
- "### Market Summary\n{market_summary}\n\n"
59
- "Write the final recommendation now.")
60
- ]
61
- )
62
- return template | model # LC chain: Prompt -> LLM
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
agents/build_graph.py DELETED
@@ -1,57 +0,0 @@
1
- import os
2
- from langgraph.graph import StateGraph, END
3
- from agents.agent_builder import make_synthesizer
4
- from agents.earnings_agent.earnings_agent import create_earnings_agent
5
- from workflow.graph_state import GraphState
6
- from langchain_openai import ChatOpenAI
7
-
8
- from agents.market_agent.market_agent import create_market_agent
9
- from agents.news_agent.news_agent import create_news_agent
10
- from workflow.nodes.nodes import supervisor_node, news_node, earnings_node, market_node, synth_node, AGENTS
11
-
12
-
13
- def build_graph(llm_model_name: str = "gpt-4o-mini"):
14
-
15
- # --- Base LLM for agents & synthesizer (swap to your provider as needed) ---
16
- openai_api_key=os.getenv("openai_api_key")
17
- llm = ChatOpenAI(api_key=openai_api_key,model=llm_model_name, temperature=0)
18
-
19
- # --- Create specialized agents ---
20
- news_agent = create_news_agent(llm)
21
- earnings_agent = create_earnings_agent(llm)
22
- market_agent = create_market_agent(llm)
23
-
24
- # --- Create synthesizer chain ---
25
- synthesizer = make_synthesizer(llm)
26
-
27
- # --- LangGraph: wire nodes ---
28
- g = StateGraph(GraphState)
29
-
30
- # Bind node callables with their dependencies via closures
31
- g.add_node("news", lambda s: news_node(s, news_agent))
32
- g.add_node("earnings", lambda s: earnings_node(s, earnings_agent))
33
- g.add_node("market", lambda s: market_node(s, market_agent))
34
- g.add_node("synth", lambda s: synth_node(s, synthesizer))
35
-
36
- # Supervisor node
37
- g.add_node("supervisor", supervisor_node)
38
- # Edges: start -> supervisor -> (news|earnings|market|synth) -> supervisor ... -> synth -> END
39
- g.set_entry_point("supervisor")
40
-
41
- for a in AGENTS:
42
- g.add_edge(a, "supervisor")
43
- g.add_edge("synth", END)
44
-
45
- # Route decisions come from the router function (returns a string)
46
- g.add_conditional_edges(
47
- "supervisor",
48
- supervisor_router, # returns: "news" | "earnings" | "market" | "synth"
49
- {
50
- "news": "news",
51
- "earnings": "earnings",
52
- "market": "market",
53
- "synth": "synth",
54
- },
55
- )
56
-
57
- return g.compile()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -1,8 +1,7 @@
1
- from agents.build_graph import build_graph
2
  from workflow.agents_workflow import build_agents_workflow
3
  from workflow.graph_state import GraphState
4
 
5
- # Run locally
6
 
7
  app = build_agents_workflow(llm_model_name="gpt-4o-mini")
8
 
@@ -19,8 +18,6 @@ def run_user_query(ticker):
19
  }
20
  final_state = app.invoke(init_state)
21
 
22
- # Update the Gradio chat history
23
-
24
  return final_state
25
 
26
  state = run_user_query("AAPL")
 
 
1
  from workflow.agents_workflow import build_agents_workflow
2
  from workflow.graph_state import GraphState
3
 
4
+ # Run locally without gradio
5
 
6
  app = build_agents_workflow(llm_model_name="gpt-4o-mini")
7
 
 
18
  }
19
  final_state = app.invoke(init_state)
20
 
 
 
21
  return final_state
22
 
23
  state = run_user_query("AAPL")
tools/__init__.py DELETED
File without changes
tools/tools.py DELETED
@@ -1,112 +0,0 @@
1
- from langchain.tools import tool
2
- import yfinance as yf
3
- import datetime as dt
4
- try:
5
- from duckduckgo_search import DDGS
6
- except Exception:
7
- DDGS = None # optional; handle gracefully below
8
-
9
- @tool("search_news", return_direct=False)
10
- def search_news_tool(query: str, max_results: int = 5) -> str:
11
- """
12
- Search latest headlines & snippets relevant to a stock or topic.
13
- Uses duckduckgo_search as a simple public news proxy.
14
- Returns a concise, newline-separated list of 'title — url'.
15
- """
16
- if DDGS is None:
17
- return ("duckduckgo_search not installed. "
18
- "Install with `pip install duckduckgo-search` "
19
- "or replace this tool with your news API.")
20
- items = []
21
- with DDGS() as ddgs:
22
- for r in ddgs.news(query, timelimit="7d", max_results=max_results):
23
- title = r.get("title", "")[:160]
24
- url = r.get("url", "")
25
- if title and url:
26
- items.append(f"{title} — {url}")
27
- if not items:
28
- return "No recent news found."
29
- return "\n".join(items)
30
-
31
-
32
- @tool("fetch_earnings", return_direct=False)
33
- def fetch_earnings_tool(ticker: str) -> str:
34
- """
35
- Fetch upcoming and recent earnings info via yfinance.
36
- Returns a concise summary (dates + surprises if available).
37
- """
38
- tk = yf.Ticker(ticker)
39
- lines = [f"EARNINGS SNAPSHOT for {ticker.upper()}"]
40
-
41
- # Upcoming earnings (earnings_dates includes future dates)
42
- try:
43
- ed = tk.earnings_dates # DataFrame if available
44
- if ed is not None and not ed.empty:
45
- # Take the next upcoming date and last reported
46
- ed_sorted = ed.sort_index()
47
- upcoming = ed_sorted[ed_sorted.index >= dt.datetime.now().date()]
48
- last = ed_sorted[ed_sorted.index < dt.datetime.now().date()]
49
- if not upcoming.empty:
50
- lines.append(f"Upcoming: {upcoming.index[0].strftime('%Y-%m-%d')}")
51
- if not last.empty:
52
- # Try EPS surprise columns if present
53
- row = last.iloc[-1]
54
- surprise = None
55
- for k in ["EPS Surprise %", "Surprise(%)", "epssurprisepct", "epssurprisepercent"]:
56
- if k in row and row[k] is not None:
57
- surprise = row[k]
58
- break
59
- lines.append(
60
- f"Last reported: {last.index[-1].strftime('%Y-%m-%d')}"
61
- + (f", EPS surprise: {surprise}" if surprise is not None else "")
62
- )
63
- else:
64
- lines.append("No earnings_dates available.")
65
- except Exception as e:
66
- lines.append(f"earnings_dates unavailable: {e}")
67
-
68
- # Quarterly financials (very high-level)
69
- try:
70
- qf = tk.quarterly_financials
71
- if qf is not None and not qf.empty:
72
- cols = list(qf.columns)
73
- if cols:
74
- last_q = cols[0]
75
- revenue = qf.loc["Total Revenue", last_q] if "Total Revenue" in qf.index else None
76
- gross_profit = qf.loc["Gross Profit", last_q] if "Gross Profit" in qf.index else None
77
- lines.append(f"Last quarter ({last_q.date()}): Revenue={revenue}, GrossProfit={gross_profit}")
78
- except Exception:
79
- pass
80
-
81
- return "\n".join(lines)
82
-
83
-
84
- @tool("fetch_market_snapshot", return_direct=False)
85
- def fetch_market_snapshot_tool(ticker: str) -> str:
86
- """
87
- Pulls basic market snapshot with yfinance: price, change, volume, valuation.
88
- Returns a compact textual snapshot.
89
- """
90
- tk = yf.Ticker(ticker)
91
- info = {}
92
- try:
93
- price = tk.fast_info.last_price
94
- prev_close = tk.fast_info.previous_close
95
- change = None
96
- if price is not None and prev_close:
97
- change = (price - prev_close) / prev_close * 100
98
- info.update({
99
- "price": price, "prev_close": prev_close, "pct_change": change,
100
- "market_cap": tk.fast_info.market_cap, "volume": tk.fast_info.last_volume,
101
- "currency": tk.fast_info.currency
102
- })
103
- except Exception as e:
104
- return f"Market snapshot failed: {e}"
105
-
106
- lines = [f"MARKET SNAPSHOT for {ticker.upper()}"]
107
- lines.append(f"Price: {info.get('price')} {info.get('currency')}")
108
- if info.get("pct_change") is not None:
109
- lines.append(f"Day change: {info['pct_change']:.2f}%")
110
- lines.append(f"Market Cap: {info.get('market_cap')}")
111
- lines.append(f"Volume: {info.get('volume')}")
112
- return "\n".join(lines)