Skip to content

Finance ReAct Agent

A ReAct agent that answers financial queries by calling tools. It demonstrates how to expose annotated Scala methods as ReAct tools and wire them into an agent that reasons over the results.

Defining the tools

case class StockQuote(ticker: String, price: Double, change_percent: Double, company: String) derives Schema

object FinanceTools:
  // Illustrative static quotes — a real implementation would call yfinance here.
  private val quotes: Map[String, StockQuote] = Map(
    "AAPL"  -> StockQuote("AAPL", 229.87, 1.24, "Apple Inc."),
    "GOOGL" -> StockQuote("GOOGL", 178.12, -0.45, "Alphabet Inc."),
    "MSFT"  -> StockQuote("MSFT", 442.57, 0.83, "Microsoft Corporation"),
    "TSLA"  -> StockQuote("TSLA", 251.44, 3.10, "Tesla, Inc.")
  )

  @description("Get current stock price and basic info.")
  def get_stock_price(ticker: String): StockQuote =
    quotes.getOrElse(ticker.trim.toUpperCase, StockQuote(ticker.toUpperCase, 0.0, 0.0, s"Unknown ($ticker)"))

  @description("Compare multiple stocks (comma-separated).")
  def compare_stocks(tickers: String): List[StockQuote] =
    tickers.split(",").iterator.map(t => get_stock_price(t)).toList

Each tool is a plain method annotated with @description. The return type is a Schema-deriving case class (or a List of them), so the tool result is structured rather than a raw string. StockQuote derives Schema, which lets the framework serialize the value back to the agent. Here the quotes are static, standing in for a live data source.

Building the agent

final class FinancialAnalysisAgent:
  private val react = ReAct(
    baseSignature = Signature.fromString("financial_query -> analysis_response"),
    tools = Vector(
      ToolFunction.fromMethod(FinanceTools.get_stock_price),
      ToolFunction.fromMethod(FinanceTools.compare_stocks)
      // NOTE: the LangChain Yahoo Finance News tool has no dspy4s bridge and is omitted.
    ),
    maxIterations = IterationLimit(6)
  )

  def forward(financialQuery: String)(using RuntimeContext): Either[DspyError, String] =
    react((financial_query = financialQuery)).map(_.output.analysis_response)

ReAct takes a base signature (financial_query -> analysis_response), the set of tools, and a maximum number of reasoning iterations. ToolFunction.fromMethod turns each annotated method into a tool: the macro derives the tool name, its description, and its argument schema from the method signature. forward runs the agent for one query and returns the analysis_response field, threading a RuntimeContext that supplies the language model.

Running it

OPENAI_API_KEY=sk-... sbt "examples/runMain dspy4s.examples.tutorials.yahoo_finance_react.yahooFinanceReactMain"

Notes

Live market data is out of scope. The tools return static illustrative quotes rather than fetching real prices, so the example runs without any market-data connection.

Full source: YahooFinanceReact.scala