AI7 Min Read

Building an AI Astrology (Kundali) Chatbot: Let the LLM Talk, Let the Ephemeris Do the Math

Gorav Singal

October 31, 2026

TL;DR

The one mistake every AI astrology bot makes is asking the LLM to compute the chart — LLMs hallucinate planetary degrees, mishandle timezones, and confuse tropical vs sidereal, so the chart comes out confidently wrong. The right architecture splits the work: the LLM extracts the birth details from natural language and interprets the results, while a DETERMINISTIC ephemeris does the astronomy. The standard library is Swiss Ephemeris (pyswisseph) — sub-arcsecond accuracy from the JPL DE data, fully reproducible, the same engine professional software uses. Expose a compute_kundali() tool via function-calling; it returns exact JSON (sidereal planet longitudes, Lagna, rasi, nakshatra+pada, Vimshottari dasha) and the LLM grounds its conversation on those numbers. The accuracy chain is unforgiving (place → lat/lon → timezone → Julian Day → ephemeris → Lahiri sidereal → nakshatra/dasha), so that part is tested code, not a prompt. Real example computed in the article: Lagna Scorpio, Moon in Rohini, Vimshottari starting in Moon dasha.

Building an AI Astrology (Kundali) Chatbot: Let the LLM Talk, Let the Ephemeris Do the Math

Ask ChatGPT “where was Saturn when I was born, 2:30 pm on 15 August 1990 in New Delhi?” and you’ll get a fluent, confident answer — that is quietly, completely made up. This is the trap at the heart of every naive AI astrology bot: people ask the language model to do astronomy, and language models are spectacularly bad at precise calculation. The result is a chatbot that talks beautifully and gets the chart wrong, which for a kundali app is the one thing you can’t afford. This article is how to build it properly — so the results are not just impressive-sounding but provably correct.

A note up front: this is an engineering article about calculation accuracy and LLM architecture. Astrology is a cultural and belief system, not a science; nothing here claims it predicts the future. Build these apps responsibly (more on that at the end).

The architecture: the LLM talks, the ephemeris calculates

The whole design rests on one decision: the LLM never computes the chart. It does the two things it’s genuinely good at — understanding a messy birth-details sentence, and explaining results in natural language — and hands the astronomy to deterministic code.

An AI astrology chatbot architecture: a user's natural-language message with birth details goes to the LLM which extracts date/time/place as JSON; geocoding and timezone resolution turn the place into lat/lon and timezone; a deterministic compute tool (Swiss Ephemeris) returns exact JSON of planet positions, Lagna, rasi, nakshatra and Vimshottari dasha; the LLM then interprets and converses grounded on the exact numbers, giving an answer that is both accurate and natural. The one rule: the LLM never does the math.

The flow is a standard tool-using agent:

  1. The LLM extracts the birth details (date, time, place) from the user’s message into structured data.
  2. Geocoding + timezone turn “New Delhi” into latitude/longitude and the correct timezone (including historical rules — a real source of bugs).
  3. A deterministic tool computes the chart with Swiss Ephemeris — this is the astronomy, and it’s exact.
  4. The LLM interprets and converses, grounded on the exact numbers the tool returned — explanation, Q&A, follow-ups.

Grounding the model on exact numbers is what turns a plausible-but-wrong astrology bot into one whose chart is correct. It’s the same pattern as any serious tool-using agent (see the AI Agent Development course and the RAG pipeline article): the model orchestrates and explains; tools provide the facts.

Why the LLM must not compute the chart

It’s worth being concrete about why you can’t skip the ephemeris and let the model estimate.

Why the LLM must not compute the chart: on the left, the LLM doing the math is wrong — it says "Saturn was around 15 degrees Capricorn", confident, fluent and made up, because LLMs can't do precise astronomy, hallucinate degrees, mishandle timezones, confuse tropical vs sidereal, and can't be reproduced. On the right, Swiss Ephemeris is correct — swe.calc_ut returns 266.1385 degrees sidereal Lahiri, sub-arcsecond accuracy from the JPL DE ephemeris, fully reproducible, the same engine professional astrology software uses, deterministic. The division of labour: the LLM is great at language and terrible at calculation; the ephemeris is the reverse; the accuracy chain under compute is place to lat/lon to timezone to Julian Day to ephemeris to sidereal to rasi/nakshatra/dasha.

A planet’s position is an astronomical computation requiring an ephemeris — a high-precision model of solar-system motion. An LLM has no such model; it pattern-matches text, so it emits a plausible number that is almost never the right number, and it can’t reproduce it. Worse, astrology-specific pitfalls — timezones, and especially tropical vs sidereal zodiacs (Vedic astrology uses sidereal) — are exactly the kind of systematic correction a model gets wrong. A chart that’s off by a few degrees puts planets in the wrong sign or nakshatra, and every downstream interpretation is then wrong.

So you give each component the job it’s good at. The ephemeris computes; the model explains. And the computation itself is a precise, unforgiving accuracy chain — place → lat/lon → timezone → Julian Day → ephemeris → sidereal correction → rasi/nakshatra/dasha — that belongs in tested code, not a prompt.

The calculation stack: Swiss Ephemeris

The “standard library” for this is Swiss Ephemeris, via the pyswisseph Python binding. It computes planetary positions to sub-arcsecond accuracy from the JPL DE ephemeris data, it’s deterministic (same input → same output, forever), and it’s the same engine most professional astrology software runs on. Here’s the core of a real compute_kundali, computing the sidereal (Vedic) chart with the standard Lahiri ayanamsa:

import swisseph as swe

def compute_kundali(year, month, day, hour, minute, tz_offset, lat, lon):
    # 1. Local time -> Universal Time -> Julian Day (the astronomy clock)
    ut = hour + minute / 60 - tz_offset              # e.g. IST = UTC+5:30
    jd = swe.julday(year, month, day, ut)

    # 2. Vedic = SIDEREAL zodiac with the Lahiri ayanamsa (NOT tropical)
    swe.set_sid_mode(swe.SIDM_LAHIRI)
    flags = swe.FLG_MOSEPH | swe.FLG_SIDEREAL

    # 3. The nine grahas (Rahu = lunar node; Ketu = node + 180)
    bodies = {"Sun": swe.SUN, "Moon": swe.MOON, "Mars": swe.MARS,
              "Mercury": swe.MERCURY, "Jupiter": swe.JUPITER,
              "Venus": swe.VENUS, "Saturn": swe.SATURN, "Rahu": swe.MEAN_NODE}
    planets = {}
    for name, b in bodies.items():
        lon_deg = swe.calc_ut(jd, b, flags)[0][0]    # sidereal longitude
        planets[name] = round(lon_deg, 4)
    planets["Ketu"] = round((planets["Rahu"] + 180) % 360, 4)

    # 4. Lagna (ascendant) from the houses
    asc = swe.houses_ex(jd, lat, lon, b'W', flags)[1][0]
    return {"ayanamsa": round(swe.get_ayanamsa_ut(jd), 4),
            "lagna": round(asc, 4), "planets": planets}

Running this for 15 Aug 1990, 14:30 IST, New Delhi gives real, reproducible output:

Ayanamsa (Lahiri): 23.7261°
Saturn: 266.1385°  →  Sagittarius · Purva Ashadha
Moon:    51.3071°  →  Taurus · Rohini (pada 4)
Sun:    118.5551°  →  Cancer · Ashlesha
Lagna:  230.2958°  →  Scorpio · Jyeshtha

Those are computed, not guessed — swe.calc_ut returns 266.1385° for Saturn every single time, to the arcsecond. From the raw longitudes you derive the rest of the chart with simple arithmetic: the Rasi (sign) is int(longitude / 30); the Nakshatra is int(longitude / (360/27)) with the pada being which of its four quarters; and the Vimshottari Dasha (the planetary-period timeline) starts from the lord of the Moon’s nakshatra, with the balance proportional to how far through that nakshatra the Moon has travelled. For our example, the Moon is in Rohini (lord: Moon), so the dasha sequence begins in Moon mahadasha (≈1.5 years of balance at birth) → Mars (7y, from 1992) → Rahu (18y, from 1999) → Jupiter (16y, from 2017) → Saturn (19y). All deterministic, all testable.

Anatomy of a kundali: a South-Indian chart of the sample birth with each planet placed in its sidereal sign — Lagna in Scorpio (highlighted), Moon in Taurus/Rohini, Sun, Jupiter, Venus and Ketu in Cancer, Mars in Aries, Mercury in Leo, Saturn in Sagittarius, Rahu in Capricorn — with a table of all nine grahas plus Lagna and their nakshatras. The pieces: 12 rasis, the Lagna, 9 grahas, 27 nakshatras, each a number the ephemeris computes exactly; the chart is just those numbers in a grid.

For the Vedic-specific pieces (divisional charts like Navamsa D9, dasha sub-periods, yogas, panchanga) you can layer your own arithmetic on Swiss Ephemeris, or use a higher-level library built on top of it — jyotisha or PyJHora for comprehensive Vedic computations, flatlib for a lighter Western-leaning toolkit. But the engine underneath is Swiss Ephemeris either way, and the principle holds: it’s code, with tests, not the LLM.

Wiring it to the LLM with function-calling

Now the LLM side. You expose compute_kundali as a tool (function-calling), describe it to the model, and let the model call it with the extracted birth details:

tools = [{
  "type": "function",
  "function": {
    "name": "compute_kundali",
    "description": "Compute an accurate Vedic birth chart (sidereal, Lahiri).",
    "parameters": {
      "type": "object",
      "properties": {
        "year": {"type": "integer"}, "month": {"type": "integer"},
        "day": {"type": "integer"}, "hour": {"type": "integer"},
        "minute": {"type": "integer"},
        "place": {"type": "string", "description": "birth city/town"}
      },
      "required": ["year","month","day","hour","minute","place"]
    }
  }
}]

The conversation then goes: user message → the model calls compute_kundali(...) → your code geocodes the place, resolves the timezone, runs Swiss Ephemeris, and returns the exact JSON → you feed that JSON back to the model → the model writes the answer, grounded on the numbers. The model decides when to compute and how to explain; it never invents a degree.

To make the interpretations themselves trustworthy (and genuinely impressive), add retrieval: index a corpus of classical rules and texts, and have the model interpret from retrieved rules rather than from its training-data memory of astrology — the RAG pattern. Now “why is this a Gajakesari yoga?” is answered from an actual rule about Jupiter and the Moon, applied to the computed positions, not a hallucinated one. Deterministic chart + retrieved rules + the LLM’s language = results that are accurate, explained, and conversational.

What actually makes the results impressive

The “wow” in a kundali chatbot comes from a few compounding things, all enabled by getting the foundation right:

  • Correctness you can verify. Users (and other astrologers) can check the chart against any professional software and it matches to the arcsecond. Trust is the whole product.
  • The full chart, instantly — planets, Lagna, nakshatras, the Vimshottari dasha timeline with real dates, divisional charts — rendered as a proper North/South-Indian diagram, computed in milliseconds.
  • Natural conversation over exact data — “what does my current dasha mean?”, “when does my Saturn period start?” — answered precisely because the model is reading real numbers, not guessing.
  • Personalisation and memory — remember the user’s chart across the session so follow-ups don’t re-ask for birth details.

None of that works if the chart is wrong. Accuracy first; everything impressive is built on it.

The pitfalls (where these apps actually break)

  • Timezone — the #1 bug. Birth time is local, but the ephemeris needs Universal Time. Get the offset wrong (DST, historical timezone changes, India’s half-hour offset) and every planet moves. Use a proper timezone library keyed to the place and date, not a hardcoded offset.
  • Tropical vs sidereal. Western astrology is tropical; Vedic is sidereal — you must set an ayanamsa (SIDM_LAHIRI). Forget it and the entire chart shifts ~24°, into the wrong signs. This is the single most common “the chart is wrong” cause.
  • Ayanamsa choice. Lahiri/Chitrapaksha is the common default, but others exist (Raman, KP). Pick one, state it, be consistent.
  • Ambiguous or unknown birth time. The Lagna and houses are very time-sensitive (the ascendant moves ~1° every 4 minutes). If the user isn’t sure of the minute, say so and offer a time-rectification caveat — don’t present a precise Lagna as certain.
  • Letting the LLM “help” with the math. The moment you let the model fill in a missing degree or “estimate” a position, correctness is gone. The tool computes everything; the model touches no numbers.

Build it responsibly

Because this sits at the intersection of software and belief, a word on ethics. Present it honestly as a cultural/astrological tool, not scientific prediction. Add clear disclaimers. Put guardrails on the LLM so it won’t give fatalistic, medical, financial or legal “predictions” (“you will get a disease”, “sell your house”) — those cause real harm; keep it to traditional chart description and general, non-deterministic language. The engineering here (accurate computation, grounded generation) is sound and genuinely interesting; use it kindly.

Takeaways

  • Never let the LLM compute the chart — it hallucinates degrees, mishandles timezones, and confuses tropical/sidereal. Use a deterministic ephemeris.
  • Swiss Ephemeris (pyswisseph) is the standard: sub-arcsecond, reproducible, sidereal with Lahiri ayanamsa — the same engine pro software uses. (Layer jyotisha/PyJHora for deeper Vedic features.)
  • Split the work: the LLM extracts birth details and interprets results; a tool computes via function-calling and returns exact JSON the model grounds on. Add RAG over classical rules for trustworthy interpretation.
  • Mind the accuracy chain (place → lat/lon → timezone → Julian Day → sidereal → nakshatra/dasha) — it’s tested code, not a prompt; timezone and tropical-vs-sidereal are the top bugs.
  • Build it responsibly: honest framing, disclaimers, and guardrails against harmful predictions.

If you want the broader patterns this builds on, see the AI Agent Development course (tool-using agents), the RAG pipeline guide (grounding generation), and Building Voicebots if you want to make the astrologer talk.

Share

Related Posts

Building an AI FAANG Interview Coach: Run the Code, Don't Let the LLM Grade It

Building an AI FAANG Interview Coach: Run the Code, Don't Let the LLM Grade It

Ask ChatGPT to “review this solution and tell me if I’d pass the interview” and…

AI Agents Demystified — It's Just Automation With a Better Brain

AI Agents Demystified — It's Just Automation With a Better Brain

Let’s cut through the noise. If you read Twitter or LinkedIn, you’d think “AI…

Building an AI Voicebot for Visitor Check-In — A Practical Guide to Handling the Messy Parts

Building an AI Voicebot for Visitor Check-In — A Practical Guide to Handling the Messy Parts

Every office lobby has the same problem: a visitor walks in, nobody’s at the…

Python SMTP Email Code - How to Send HTML Email from Python Code with Authentication at SMTP Server

Python SMTP Email Code - How to Send HTML Email from Python Code with Authentication at SMTP Server

Introduction This post has the complete code to send email through smtp server…

Django Python - How to Build Docker Image and Run Web-service on Apache with Python 3.9

Django Python - How to Build Docker Image and Run Web-service on Apache with Python 3.9

Introduction So you have a Django project, and want to run it using docker image…

Python Build Quality Gates with Pylint and Unittest Coverage Thresholds

Python Build Quality Gates with Pylint and Unittest Coverage Thresholds

Introduction It is very important to introduce few process so that your code and…

Latest Posts

Building an AI FAANG Interview Coach: Run the Code, Don't Let the LLM Grade It

Building an AI FAANG Interview Coach: Run the Code, Don't Let the LLM Grade It

Ask ChatGPT to “review this solution and tell me if I’d pass the interview” and…

Execution Algorithms: VWAP & TWAP in Python

Execution Algorithms: VWAP & TWAP in Python

Every strategy so far produced signals — when to be long, short, or flat. But a…

Position Sizing & the Kelly Criterion in Python

Position Sizing & the Kelly Criterion in Python

Every strategy article so far asked ”what should I trade?” This one asks the…

Order-Book Imbalance: A Microstructure Signal (in Python)

Order-Book Imbalance: A Microstructure Signal (in Python)

The strategies so far used daily or minute bars. This last one goes down to the…

The Moving-Average Crossover, Backtested Honestly in Python

The Moving-Average Crossover, Backtested Honestly in Python

If algo trading has a “hello world,” it’s the moving-average crossover. Every…

Mean Reversion & Pairs Trading in Python

Mean Reversion & Pairs Trading in Python

The moving-average crossover bet on direction and lost. Here’s a strategy with a…