Repository navigation
feat(examples): add FXMacroData macro research example #1438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
roberttidball
wants to merge
6
commits into
VoltAgent:main
Choose a base branch
from
fxmacrodata:examples/with-fxmacrodata
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
4d1f7fb
feat(examples): add FXMacroData macro research example
roberttidball beceaae
fix(examples): refuse redirects, keep the API key out of errors, chec…
roberttidball 7471eae
fix(examples): NodeNext resolution, stricter dates and slugs, catalog…
roberttidball 26d53d8
docs(examples): say which with-fxmacrodata requests need an FXMacroDa…
roberttidball e560fa3
fix(examples): tell the model keyless FXMacroData releases are delaye…
roberttidball b92b800
fix(examples): bind the with-fxmacrodata server to 127.0.0.1
roberttidball File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| OPENAI_API_KEY=your_openai_api_key_here | ||
| # Optional: USD data works without a key | ||
| FXMACRODATA_API_KEY= | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| # VoltAgent with FXMacroData | ||
|
|
||
| This example gives a VoltAgent research agent tools for official macroeconomic data from [FXMacroData](https://fxmacrodata.com/documentation/reference?utm_source=github&utm_medium=referral&utm_campaign=voltagent&utm_content=readme): indicator releases with their publication times, release calendars and FX rates. | ||
|
|
||
| ## Features | ||
|
|
||
| - List the indicators available for a currency (CPI, GDP, policy rate, payrolls, bond yields and more) | ||
| - Fetch released values with the period, the publication timestamp and the official source link | ||
| - Fetch the upcoming release calendar, with times in UTC and the publisher's local time | ||
| - Fetch daily FX rates for a currency pair | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| 1. **OpenAI API Key**: Used by the example agent model | ||
| 2. **FXMacroData API Key** (optional): USD releases, the USD calendar and the indicator catalogue work without a key. Other currencies and FX rates need one, see [plans](https://fxmacrodata.com/subscribe?utm_source=github&utm_medium=referral&utm_campaign=voltagent&utm_content=readme) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| ## Setup | ||
|
|
||
| 1. Install dependencies: | ||
|
|
||
| ```bash | ||
| pnpm install | ||
| ``` | ||
|
|
||
| 2. Configure environment variables: | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| Then edit `.env`: | ||
|
|
||
| ```env | ||
| OPENAI_API_KEY=your_openai_api_key_here | ||
| FXMACRODATA_API_KEY= | ||
| ``` | ||
|
|
||
| 3. Run the example: | ||
|
|
||
| ```bash | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| The agent runs on the default VoltAgent server port and exposes one agent named `macroResearchAgent`. | ||
|
|
||
| ## Tools Available | ||
|
|
||
| ### 1. List Indicators | ||
|
|
||
| - **Purpose**: List indicator slugs, names, units and sources for a currency | ||
| - **Endpoint**: `GET /v1/data_catalogue/{currency}` | ||
|
|
||
| ### 2. Get Indicator History | ||
|
|
||
| - **Purpose**: Released values for one indicator, newest first | ||
| - **Endpoint**: `GET /v1/announcements/{currency}/{indicator}` | ||
| - **Parameters**: | ||
| - `currency`: Three-letter code such as `usd` | ||
| - `indicator`: Slug such as `inflation` or `policy_rate` | ||
| - `startDate`, `endDate`: Optional `YYYY-MM-DD` bounds | ||
| - `limit`: Maximum rows (default 12) | ||
|
|
||
| ### 3. Get Release Calendar | ||
|
|
||
| - **Purpose**: Scheduled releases for a currency | ||
| - **Endpoint**: `GET /v1/calendar/{currency}` | ||
| - **Parameters**: | ||
| - `indicator`: Optional slug filter | ||
| - `startDate`, `endDate`: Optional `YYYY-MM-DD` bounds | ||
|
|
||
| ### 4. Get FX Rates | ||
|
|
||
| - **Purpose**: Daily rates for a currency pair (needs an API key) | ||
| - **Endpoint**: `GET /v1/forex/{base}/{quote}` | ||
|
|
||
| ## Example Prompts | ||
|
|
||
| - "What was the latest US CPI print and when was it published?" | ||
| - "When are the next US payrolls and CPI releases?" | ||
| - "How has the Fed's policy rate moved this year?" | ||
| - "Compare the latest euro area and UK inflation" (needs an API key) | ||
|
|
||
| ## Notes | ||
|
|
||
| The tools return the API response unchanged, so the agent sees units, source links and data-quality fields as published. Responses are not cached; the keyless tier has a fair-use allowance of 100 requests per day. | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| { | ||
| "name": "voltagent-example-with-fxmacrodata", | ||
| "author": "", | ||
| "dependencies": { | ||
| "@voltagent/cli": "^0.1.21", | ||
| "@voltagent/core": "^2.11.0", | ||
| "@voltagent/logger": "^2.0.2", | ||
| "@voltagent/server-hono": "^2.0.14", | ||
| "ai": "^6.0.0", | ||
| "zod": "^3.25.76" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/node": "^24.2.1", | ||
| "tsx": "^4.21.0", | ||
| "typescript": "^5.8.2" | ||
| }, | ||
| "keywords": [ | ||
| "agent", | ||
| "ai", | ||
| "economic-calendar", | ||
| "fxmacrodata", | ||
| "macroeconomics", | ||
| "voltagent" | ||
| ], | ||
| "license": "MIT", | ||
| "private": true, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/VoltAgent/voltagent.git", | ||
| "directory": "examples/with-fxmacrodata" | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "dev": "tsx watch --env-file=.env ./src", | ||
| "start": "node dist/index.js", | ||
| "volt": "volt" | ||
| }, | ||
| "type": "module" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { Agent, VoltAgent } from "@voltagent/core"; | ||
| import { createPinoLogger } from "@voltagent/logger"; | ||
| import { honoServer } from "@voltagent/server-hono"; | ||
| import { fxmacrodataTools } from "./tools"; | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| const logger = createPinoLogger({ | ||
| name: "fxmacrodata-agent", | ||
| level: "info", | ||
| }); | ||
|
|
||
| const macroResearchAgent = new Agent({ | ||
| name: "macro-research-agent", | ||
| instructions: `You answer macroeconomic and FX questions with FXMacroData tools. | ||
|
|
||
| Use listIndicators when you need an indicator slug, getIndicatorHistory for released values, | ||
| getReleaseCalendar for what is scheduled next, and getFxRates for exchange rates. | ||
| Quote each value with the period it refers to and when it was published, and cite the source link. | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
| If a tool returns HTTP 401 or 403, say that the currency or dataset needs an FXMacroData API key | ||
| rather than guessing a number.`, | ||
| model: "openai/gpt-4o-mini", | ||
| tools: fxmacrodataTools, | ||
| }); | ||
|
|
||
| new VoltAgent({ | ||
| agents: { | ||
| macroResearchAgent, | ||
| }, | ||
| logger, | ||
| server: honoServer(), | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| import { createTool } from "@voltagent/core"; | ||
| import { z } from "zod"; | ||
|
|
||
| const FXMACRODATA_BASE_URL = "https://api.fxmacrodata.com/v1"; | ||
| const FXMACRODATA_REQUEST_TIMEOUT_MS = 20_000; | ||
|
|
||
| type FXMacroDataQueryParams = Record<string, number | string | undefined>; | ||
|
|
||
| type FXMacroDataResult = { | ||
| data?: unknown; | ||
| error?: string; | ||
| status?: number; | ||
| success: boolean; | ||
| }; | ||
|
|
||
| const currencySchema = z.string().regex(/^[A-Za-z]{3}$/, "Use a three-letter ISO currency code."); | ||
|
|
||
| /** | ||
| * Lower-cases a currency code for use in a URL path segment. | ||
| */ | ||
| function code(value: string): string { | ||
| return value.trim().toLowerCase(); | ||
| } | ||
|
|
||
| const dateSchema = z | ||
| .string() | ||
| .regex(/^\d{4}-\d{2}-\d{2}$/, "Use YYYY-MM-DD.") | ||
| .optional(); | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Calls a read-only FXMacroData REST endpoint and returns a tool-friendly result. | ||
| * | ||
| * FXMACRODATA_API_KEY is optional: USD releases, the USD calendar and the data | ||
| * catalogue answer without a key. Other currencies and FX rates need one. | ||
| */ | ||
| export async function callFXMacroData( | ||
| path: string, | ||
| params: FXMacroDataQueryParams = {}, | ||
| ): Promise<FXMacroDataResult> { | ||
| const url = new URL(`${FXMACRODATA_BASE_URL}${path}`); | ||
| for (const [key, value] of Object.entries(params)) { | ||
| if (value !== undefined && value !== "") { | ||
| url.searchParams.set(key, String(value)); | ||
| } | ||
| } | ||
|
|
||
| const headers: Record<string, string> = { accept: "application/json" }; | ||
| const apiKey = process.env.FXMACRODATA_API_KEY; | ||
| if (apiKey) { | ||
| headers["x-api-key"] = apiKey; | ||
| } | ||
|
|
||
| const controller = new AbortController(); | ||
| const timeout = setTimeout(() => controller.abort(), FXMACRODATA_REQUEST_TIMEOUT_MS); | ||
|
|
||
| try { | ||
| const response = await fetch(url, { headers, method: "GET", signal: controller.signal }); | ||
|
|
||
| if (!response.ok) { | ||
| const body = (await response.text()).slice(0, 500); | ||
| return { | ||
| success: false, | ||
| status: response.status, | ||
| error: `FXMacroData API returned HTTP ${response.status}: ${body || response.statusText}`, | ||
| }; | ||
| } | ||
|
|
||
| return { | ||
| success: true, | ||
| data: await response.json(), | ||
| }; | ||
| } catch (error) { | ||
| return { | ||
| success: false, | ||
| error: error instanceof Error ? error.message : "FXMacroData request failed.", | ||
| }; | ||
| } finally { | ||
| clearTimeout(timeout); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Lists the indicator slugs available for a currency. | ||
| */ | ||
| export const listIndicatorsTool = createTool({ | ||
| name: "listIndicators", | ||
| description: | ||
| "List the macroeconomic indicators available for a currency, with names, units and sources. Call this first when you do not know an indicator slug.", | ||
| parameters: z.object({ | ||
| currency: currencySchema.describe('Three-letter currency code, such as "usd" or "eur".'), | ||
| }), | ||
| execute: async ({ currency }) => callFXMacroData(`/data_catalogue/${code(currency)}`), | ||
| }); | ||
|
|
||
| /** | ||
| * Fetches released values for one indicator, with release timestamps and source links. | ||
| */ | ||
| export const getIndicatorHistoryTool = createTool({ | ||
| name: "getIndicatorHistory", | ||
| description: | ||
| "Fetch released values for one indicator (for example inflation, policy_rate, gdp, unemployment) with the date each value refers to, when it was published, and the official source link.", | ||
| parameters: z.object({ | ||
| currency: currencySchema.describe('Three-letter currency code, such as "usd".'), | ||
| indicator: z | ||
| .string() | ||
| .min(1) | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
| .describe('Indicator slug from listIndicators, such as "inflation" or "policy_rate".'), | ||
| startDate: dateSchema.describe("Optional start date, YYYY-MM-DD."), | ||
| endDate: dateSchema.describe("Optional end date, YYYY-MM-DD."), | ||
| limit: z.number().int().min(1).max(100).optional().describe("Maximum rows to return."), | ||
| }), | ||
| execute: async ({ currency, indicator, startDate, endDate, limit = 12 }) => | ||
| callFXMacroData( | ||
| `/announcements/${code(currency)}/${encodeURIComponent(indicator.trim().toLowerCase())}`, | ||
| { start_date: startDate, end_date: endDate, limit }, | ||
| ), | ||
| }); | ||
|
|
||
| /** | ||
| * Fetches the upcoming release calendar for a currency. | ||
| */ | ||
| export const getReleaseCalendarTool = createTool({ | ||
| name: "getReleaseCalendar", | ||
| description: | ||
| "Fetch scheduled economic releases for a currency, with release times in UTC and the publisher's local time. Optionally filter to one indicator.", | ||
| parameters: z.object({ | ||
| currency: currencySchema.describe('Three-letter currency code, such as "usd".'), | ||
| indicator: z.string().optional().describe('Optional indicator slug, such as "inflation".'), | ||
| startDate: dateSchema.describe("Optional start date, YYYY-MM-DD."), | ||
| endDate: dateSchema.describe("Optional end date, YYYY-MM-DD."), | ||
| }), | ||
| execute: async ({ currency, indicator, startDate, endDate }) => | ||
| callFXMacroData(`/calendar/${code(currency)}`, { | ||
| indicator: indicator?.trim().toLowerCase(), | ||
| start_date: startDate, | ||
| end_date: endDate, | ||
| }), | ||
| }); | ||
|
|
||
| /** | ||
| * Fetches daily FX rates for a currency pair. | ||
| */ | ||
| export const getFxRatesTool = createTool({ | ||
| name: "getFxRates", | ||
| description: | ||
| "Fetch daily FX rates for a currency pair, such as EUR/USD. Requires an FXMacroData API key.", | ||
| parameters: z.object({ | ||
| base: currencySchema.describe('Base currency, such as "eur".'), | ||
| quote: currencySchema.describe('Quote currency, such as "usd".'), | ||
| startDate: dateSchema.describe("Optional start date, YYYY-MM-DD."), | ||
| endDate: dateSchema.describe("Optional end date, YYYY-MM-DD."), | ||
| limit: z.number().int().min(1).max(100).optional().describe("Maximum rows to return."), | ||
| }), | ||
| execute: async ({ base, quote, startDate, endDate, limit = 20 }) => | ||
| callFXMacroData(`/forex/${code(base)}/${code(quote)}`, { | ||
| start_date: startDate, | ||
| end_date: endDate, | ||
| limit, | ||
| }), | ||
| }); | ||
|
|
||
| export const fxmacrodataTools = [ | ||
| listIndicatorsTool, | ||
| getIndicatorHistoryTool, | ||
| getReleaseCalendarTool, | ||
| getFxRatesTool, | ||
| ]; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2022", | ||
| "module": "ESNext", | ||
| "moduleResolution": "bundler", | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
|
||
| "outDir": "dist", | ||
| "esModuleInterop": true, | ||
| "allowSyntheticDefaultImports": true, | ||
| "strict": true, | ||
| "skipLibCheck": true, | ||
| "declaration": true, | ||
| "declarationMap": true, | ||
| "sourceMap": true | ||
| }, | ||
| "include": ["src/**/*"] | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.