DEV Community

Oliver S
Oliver S

Posted on

From $USD to €EUR to MXN$: A Self-Improving AI Agent That Speaks All Currencies

Project: Multi-Currency Invoice

What this AI agent actually does?

This self-improving AI agent takes multi-currency invoices, extracts all data, and automatically normalizes all monetary values to a target currency (header currency) using historical exchange rates based on the invoice issue date. The crazy part? It gets smarter the more you use it.

Full code open source at: https://github.com/Handit-AI/handit-examples/tree/main/examples/multi-currency-invoice

Let's dive in!

Table of Contents

1. It gets smarter the more you use it (Best Part)

I'm going to start with the best part — the cherry on top

Here’s a really cool thing — this AI agent actually gets better over time. Here is the secret weapon Handit.ai

Every action, every response is fully observed and analyzed. The system can see:

  • Which fields contain monetary values
  • What currencies are being used
  • When conversion is needed
  • How to apply real-time exchange rates
  • Whether normalization was successful
  • LLM output accuracy
  • LLM output format
  • And more...

And yes sir! When this powerful tool detects any mistakes it fixes automatically.

This means the AI agent can actually gets smarter the more you use it. If the LLM extracts the wrong field or generates incorrect outputs, Handit.ai tracks that failure and automatically adjusts the AI agent to prevent the same mistake from happening again. It's like having an AI engineer who is constantly monitoring, evaluating and improving your AI agent.

Here are the results before and after processing:

Before Handit

Prompt:

`You are a financial data processor. Your task is to normalize all monetary values in the following JSON data to ${targetCurrency}.

IMPORTANT: Use the convert_currency tool whenever you encounter a monetary value that needs to be converted to ${targetCurrency}.

The data structure follows this pattern:
- Each field has a "value" property containing the actual data
- Monetary values may be in various currencies (USD, EUR, GBP, etc.)
- You need to identify monetary values and convert them to ${targetCurrency}
- Use the convert_currency tool for each conversion needed

JSON Data to process:
${JSON.stringify(data, null, 2)}

Instructions:
1. Analyze the JSON data to identify all monetary values
2. For each monetary value that is NOT in ${targetCurrency}, use the convert_currency tool
3. The tool will return conversion results that you should use to add a "normalizedValue" field
4. Preserve the original structure completely - only add the normalizedValue field
5. If a value is already in ${targetCurrency}, you can skip conversion but still add a normalizedValue field with the same values

Remember: Always use the convert_currency tool for conversions. Do not attempt to convert currencies manually.`
Enter fullscreen mode Exit fullscreen mode

Input: Multi-currency Invoice with mixed currencies

Ouput:

{
   "extractedData": {
    "documentType": {
      "value": "invoice",
      "confidence": 0.98,
      "reason": "Large header text 'INVOICE' at top of document (clear OCR region)."
    },
    "headerCurrency": {
      "value": "SGD",
      "confidence": 0.62,
      "reason": "The printed final/Total line shows '5000 SGD' which indicates the invoice's presented total currency. Document contains multiple currencies, but the final Total is in SGD."
    },
    "invoiceDate": {
      "value": "16-04-2024",
      "confidence": 0.99,
      "reason": "Date field shows '04/16/2024' near the top; converted to DD-MM-YYYY as requested."

...

    "lineItems": [
      {
        "description": "Consulting",
        "quantity": null,
        "rate": null,
        "amount": "5,000.00",
        "currency": "SEK",
        "confidence": 0.82,
        "reason": "Row shows 'Consulting' with amount '5,000.00 SK'. Interpreted as 5,000.00 SEK (subtotal later also shows SEK), symbol truncated in row but subtotal confirms SEK."
      },
      {
        "description": "Software License",
        "quantity": null,
        "rate": null,
        "amount": "150,000",
        "currency": "JPY",
        "confidence": 0.95,
        "reason": "Row shows '¥150,000' next to 'Software License' — yen symbol indicates Japanese Yen."
      },
      {
        "description": "Maintenance",
        "quantity": null,
        "rate": null,
        "amount": "1,000.0",
        "currency": "CAD",
        "confidence": 0.94,
        "reason": "Row displays '1,000.0 CAD' beside 'Maintenance' (explicit CAD currency code)."
      },

...

    "_conversions": [
    {
      "originalArgs": {
        "amount": 150000,
        "fromCurrency": "JPY",
        "toCurrency": "SGD"
      },
      "result": {
        "convertedAmount": 1309.5,
        "originalAmount": 150000,
        "fromCurrency": "JPY",
        "toCurrency": "SGD",
        "exchangeRate": 0.00873,
        "conversionDate": "2025-09-01",
        "method": "api_conversion",
        "apiResponse": {
          "originalAmount": 150000,
          "originalCurrency": "JPY",
          "convertedAmount": 1309.5,
          "targetCurrency": "SGD",
          "exchangeRate": 0.00873,
          "date": "2025-09-01",
          "success": true,
          "apiVersion": "v4",
          "historical": false
        }
      },
      "timestamp": "2025-09-01T20:35:55.758Z",
      "status": "unmatched"
    },
    {
      "originalArgs": {
        "amount": 1000,
        "fromCurrency": "CAD",
        "toCurrency": "SGD"
      },
      "result": {
        "convertedAmount": 934,
        "originalAmount": 1000,
        "fromCurrency": "CAD",
        "toCurrency": "SGD",
        "exchangeRate": 0.934,
        "conversionDate": "2025-09-01",
        "method": "api_conversion",
        "apiResponse": {
          "originalAmount": 1000,
          "originalCurrency": "CAD",
          "convertedAmount": 934,
          "targetCurrency": "SGD",
          "exchangeRate": 0.934,
          "date": "2025-09-01",
          "success": true,
          "apiVersion": "v4",
          "historical": false
        }
      },
      "timestamp": "2025-09-01T20:35:55.856Z",
      "status": "unmatched"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Whoa! It looks like the output isn’t what we expected

First issue ❌: The extracted output does not adhere to the system prompt's requirement to add a 'normalizedValue' field for each monetary value
Second issue ❌: There is an array at the end called _conversions, but it doesn’t contain all the conversions

Results:

Accuracy: 44%
Correctness Evaluation: 48%

Lack of clarity on output format for monetary values.

After Handit

First, let’s see what results Handit gives us

Here it is — it highlights several insights, including the fact that the extracted output does not adhere to the system prompt's requirement to add a 'normalizedValue' field.

HERE IS THE BEST PART!

It automatically sent us a PR with the fixes from our AI agent, ready to deploy, reaching an accuracy of 90%!

After merging the PR, here are the results:

Input: Same Multi-currency Invoice

Output: All amounts normalized to SGD with normalizedValue fields

{
  "extractedData": {
    "lineItems": [
      {
        "description": "Consulting",
        "quantity": null,
        "rate": null,
        "amount": "5,000.00",
        "currency": "SEK",
        "fromCurrency": "SEK",
        "toCurrency": "SGD",
        "confidence": 0.82,
        "reason": "Row shows 'Consulting' with amount '5,000.00 SEK'. Interpreted as 5,000.00 SEK (subtotal later also shows SEK).",
        "normalizedValue": 683.41
      },
      {
        "description": "Software License",
        "quantity": null,
        "rate": null,
        "amount": "150,000",
        "currency": "JPY",
        "fromCurrency": "JPY",
        "toCurrency": "SGD",
        "confidence": 0.95,
        "reason": "Row shows '¥150,000' next to 'Software License' — yen symbol indicates Japanese Yen.",
        "normalizedValue": 1309.5
      },
      {
        "description": "Maintenance",
        "quantity": null,
        "rate": null,
        "amount": "1,000.0",
        "currency": "CAD",
        "fromCurrency": "CAD",
        "toCurrency": "SGD",
        "confidence": 0.94,
        "reason": "Row displays '1,000.0 CAD' beside 'Maintenance' (explicit CAD currency code).",
        "normalizedValue": 934
      },

...

    ],
...
  }
}


}
Enter fullscreen mode Exit fullscreen mode

The mistakes have been fixed automatically!

All monetary values now have normalizedValue fields with:

  • Converted amounts in SGD
  • Original amounts preserved

Results:

Accuracy: 90%
Correctness Evaluation: 90%
Overall Performance Boost: ↗️ +46%

2. Architecture Overview

So I built this thing called "Multi-Currency Invoice", and honestly, it's doing some pretty wild stuff. Let me break down what's actually happening under the hood.

Let's understand the architecture of our AI agent at a very high level:

This architecture separates concerns into distinct nodes:

  1. Ingestion

    • Purpose: Manages file uploads and session organization for invoice processing
    • Input: Images, PDFs, documents (max 10 files, 50MB each)
    • Output: Organized file storage with session directories
    • Core capability: File ingestion pipeline with automatic session management
  2. Extraction

    • Purpose: AI analyzes uploaded documents and extracts all data from invoices and other financial documents
    • Input: Images, PDFs, Office documents with base64 encoding support
    • Output: Structured JSON containing all extracted fields and confidence scores
    • AI capability: Multimodal analysis (vision + text), parallel processing, and VLLM support
  3. CurrencyNormalization

    • Purpose: AI normalizes monetary values across different currencies to a target currency using intelligent analysis
    • Input: Structured JSON containing all fields extracted in the previous step
    • Output: Normalized JSON containing all fields, converted values, and applied exchange rates
    • API capability: Historical exchange rates based on the invoice issue date
    • AI capability: Detects monetary value that needs to be converted

And... How does this AI agent gets better over time?

Here is the secret weapon: Handit.ai

  1. Observability
    • Every interaction with this AI agent is monitored by handit
  2. Failure Detection
    • Handit automatically identifies errors in any of our LLMs — like when a monetary value was not normalized correctly (Really important for this AI agent)
  3. Automated Fix Generation
    • If a failure is detected, Handit automatically fixes our prompts for us

Want to deep dive into the tools, prompts, and nodes? Check the repo here: https://github.com/Handit-AI/handit-examples/tree/main/examples/unstructured-to-structured

3. Stack

Tech Stack

  • Framework: Node.js 18+, Express 4 (MVC)
  • Middleware: helmet, cors, morgan, express.json/urlencoded, multer (uploads)
  • Config/Runtime typing: dotenv, zod
  • AI/LLM: LangChain (langchain, @langchain/openai); OpenAI models (OPENAI_MODEL) — default gpt-5-mini-2025-08-07 (extraction) and gpt-4o-mini (normalization)
  • Observability, Evaluation and Optimization: @handit.ai/node

External APIs

  • OpenAI API

    • Purpose: LLM for document data extraction (multimodal) and currency normalization with tools
    • Access: LangChain/OpenAI SDKs
    • Auth: OPENAI_API_KEY
  • ExchangeRate-API

    • Purpose: Latest and historical FX rates
    • Endpoints:
    • v4 (free, latest): https://api.exchangerate-api.com/v4/latest/{base}
    • v6 (historical, with key): https://v6.exchangerate-api.com/v6/{API_KEY}/history/{base}/{year}/{month}/{day}[/{amount}]
    • Auth: EXCHANGE_RATE_API_KEY (required for v6)
  • Handit.ai

    • Purpose: Execution tracing/observability, Evaluation, Optimization
    • Access: @handit.ai/node SDK
    • Auth: HANDIT_API_KEY (required to start server)

4. Conclusions

Thanks for reading!

I hope this deep dive into building a self-improving AI agent that speaks all currencies has been valuable for you

The project is fully open source - feel free to:
🔧 Modify it for your specific needs
🏭 Adapt it to any industry you want
🚀 Use it as a foundation for your own AI agents
🤝 Contribute improvements back to the community

Full code open source at: https://github.com/Handit-AI/handit-examples/tree/main/examples/multi-currency-invoice

This project demonstrates how to:

  • Integrate LangChain with real APIs for intelligent document processing
  • Use LLM tools for automated currency conversion
  • Handle multi-currency documents intelligently
  • Preserve document structure while adding normalized values
  • Fetch real-time exchange rates for accurate conversions
  • Build a self-improvement AI agent gets smarter the more you use it

This project comes with Handit.ai configured. If you want to configure Handit.ai for your own projects, I suggest following the documentation: https://docs.handit.ai/quickstart

Top comments (0)