Building an astrological GPT
Tags: chatgpt, gpts, typescript, ai
Published: 2023-12-28 18:42:07

I'm not a believer in astrology in the slightest, but I've long been fascinated by it as a coder. When I was a kid just learning to code for the first time, a friend of the family was a professional astrologer, writing the horoscopes for a well known weekly women's magazine in the UK. Contrary to popular belief, he didn't just make them up, but actually carefully calculated the current celestial map using ephemeris software, which he then interpreted. I was fascinated with the charts that he'd noisily print off on the his dot matrix printer. Fast forward to today and this intersection of highly specific calculations and fantastical storytelling seems like a perfect candidate for a custom GPT using the OpenAI GPT API. In this post I'm going to show how to build an ephemeris API, and a custom GPT that uses it for a custom action. I'll also show how to use instructions to give responses in the proper astrological style.

If you have ChatGPT Plus, you can try the end result [in ChatGPT](https://chatgpt.com/g/g-dUZg6nVPD-luna-astrology-horoscopes-and-birth-charts). See the finished code for the API [in GitHub](https://github.com/ascorbic/mysticml).

To perform their readings, astrologers use an ephemeris to show the locations of all of the celestial bodies visible in the sky at a certain time and place. These used to be huge tables of pre-calculated coordinates, but even in the 1980s they were computerised. Today they can be easily calculated with any of many libraries. We can create an Action to provide this data to the GPT.

I'm going to create a Netlify Edge Function for this, because I like the API and it can give quick responses close to the OpenAI server. I'm using the [Moshier ephemeris](https://github.com/0xStarcat/Moshier-Ephemeris-JS) library because it doesn't need pregenerated data and performs all of the calculations locally. Netlify Edge Functions are based on Deno, so we can load it directly from a URL import using esm.sh:

```typescript
// netlify/edge-functions/ephemeris.ts
import Ephemeris from "https://esm.sh/gh/0xStarcat/Moshier-Ephemeris-JS/src/Ephemeris.js";

export default async function handler(request: Request) {
    // do stuff
}

export const config = {
	path: "/ephemeris",
};

```

To get the ephemeris results, we need the location, date and time, so we'll these in the request. The ephemeris library needs the date and time parts to be passed in individually, so we'll parse the datetime string and then extract these values. If the user doesn't pass in the date then we will default to now.

```typescript
export default async function handler(req: Request) {

	const url = new URL(req.url);
	const datetimeString = url.searchParams.get("datetime");
	const latitude = parseFloat(url.searchParams.get("latitude")!);
	const longitude = parseFloat(url.searchParams.get("longitude")!);

	if (isNaN(latitude) || isNaN(longitude)) {
		return new Response("Invalid parameters", { status: 400 });
	}

	const datetime = datetimeString ? new Date(datetimeString) : new Date();
	const year = datetime.getUTCFullYear();
	const month = datetime.getUTCMonth();
	const day = datetime.getUTCDate();
	const hours = datetime.getUTCHours();
	const minutes = datetime.getUTCMinutes();
```

By default, the ephemeris library returns values for all of the celestial bodies that it knows about, but we can let ChatGPT choose the ones it's interested in, while defaulting to returning all of them:

```typescript
const ALL_BODIES = [
	"mercury",
	"venus",
	"mars",
	"jupiter",
	"saturn",
	"uranus",
	"neptune",
	"pluto",
	"sun",
	"moon",
	"chiron",
];


export default async function handler(req: Request) {

	const url = new URL(req.url);
	let celestialBodies = url.searchParams.get("bodies")?.split(",");
	if (!celestialBodies || celestialBodies.length === 0) {
		celestialBodies = ALL_BODIES;
	}
```

Now we have all of the details we need, we can pass these to the library and return the results:


```typescript
	const ephemeris = new Ephemeris({
		year,
		month,
		day,
		hours,
		minutes,
		latitude,
		longitude,
		calculateShadows: false,
	});

	const results: Record<string, unknown> = {};
	for (const body of celestialBodies) {
		if (ephemeris[body]) {
			results[body] = ephemeris[body].position;
		} else {
			results[body] = "Celestial body not found";
		}
	}

	return Response.json(results);
```

This gives us the data we need, and returns it. See the full code [here](https://github.com/ascorbic/mysticml/blob/main/netlify/edge-functions/ephemeris.ts).

Creating a repo with just that file is enough to deploy to Netlify, but I've added a static `index.html` file too, as well as a privacy policy because we'll need that later. Thanks ChatGPT for both. You can deploy your own copy by clicking this:

[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg?1)](https://app.netlify.com/start/deploy?repository=https://github.com/ascorbic/mysticml)

We're almost ready to start creating the custom GPT. To register an API as an action for a GPT, we need an OpenAPI spec for it. These are really verbose an annoying to write, so I usually just ask ChatGPT to do it for me. There's no need to go into detail typing the full response - ChatGPT can work it out for itself. You can see what we generated [here](https://github.com/ascorbic/mysticml/blob/main/static/openapi.yml). I've deployed it as part of the site, but that's not needed. Remember to change the URL to that of your own site!

Now we're ready to go. Let's create a GPT. Go to https://chat.openai.com/gpts/editor to start. You can find it linked from "Explore" in the ChatGPT sidebar.


![Screenshot of the new GPT editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n3xvmy4hpov4ksqr7kza.png)

The editor is quite simple to use, because it does most things for you via the chat interface. However, we should start by defining the Action. Click on "Configure", then scroll down to the bottomn, enable "Code Interpreter", the click "Create new action".

![Screenshot showing the above](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k3zx8v8ffqc2fzr2bu7a.png)

You then need to define your schema. If you deployed it earlier then you can just choose "import from URL". Otherwise paste it in. You might get an error message - if so, just share it with the ChatGPT session where you created the schema and it should fix it for you.

![Screenshot for the "Add action" page, showing the schema section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nore64gj4am1pl00k5fo.png)

If you get no errors, then you can click on "Test" to try it out.

Now we get to the fun bit. Go back to the "Create" tab and just tell it what you want. You should include all sorts of detail, including the tone for the responses and how exactly to use the APIs.


![You are an astrologer who interprets users' birth charts and gives readings for their horoscopes. You speak in a mystical tone and are happy to discuss all topics sensitively. If the user asks for personalised responses, first ask for their date, time and location of birth. Assure them that we don't log or store this data. If they prefer you can give a general reading from their sun sign. You should then make two requests to the API. First, for the user's birth chart, then for the current date. Calculate the aspects, houses etc and then give your interpretation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c3p5tgovodam7fgev9pb.png)

ChatGPT will then use this to customise the GPT. That prompt gave these results:

![Configure screen, showing name, description and conversation starters](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/py76kcwfzyvgtp5ruz16.png)

You can try this out, and adjust the details as you wish. You can ask for a profile pic. As long as you included the privacy policy when creating the action, you can then publish your GPT. Enjoy! [Try it out here](https://chatgpt.com/g/g-dUZg6nVPD-luna-astrology-horoscopes-and-birth-charts)