-
-
Save ninjudd/a6936ce00b6cfc37d5a75a958435c3aa to your computer and use it in GitHub Desktop.
| const selectTopic = functor({ | |
| prompt: ({ query, topics }: { query: string; topics: string }) => ` | |
| User query: ${query} | |
| Existing topics: | |
| ${topics} | |
| Match an existing topic if one fits, otherwise create a new one. | |
| `, | |
| returns: [ | |
| { | |
| type: 'choose_existing', | |
| description: 'Pick an existing topic that matches', | |
| schema: z.object({ topicId: z.string() }), | |
| }, | |
| { | |
| type: 'create_new', | |
| description: 'Create a new topic when none match', | |
| schema: z.object({ | |
| name: z.string(), | |
| summary: z.string(), | |
| }), | |
| }, | |
| ], | |
| }); |
| const extractPatient = functor({ | |
| prompt: ({ text }: { text: string }) => ` | |
| Extract patient info from the text below. | |
| Return whatever fields are present. | |
| ${text} | |
| `, | |
| returns: { | |
| description: 'Extract basic patient demographics if present', | |
| schema: z.object({ | |
| name: z.string().optional(), | |
| birthDate: z.string().optional(), | |
| gender: z.string().optional(), | |
| }), | |
| }, | |
| }); | |
| const patient = await extractPatient({ text: documentText }); |
The schema is the return type. The prompt is the implementation.
What followed was realizing how much of a codebase can be written this way.
A lot of regexes and hand-rolled parsers were just deterministic answers to non-deterministic questions.
Prompts are just another programming language now.
You write functions in prose, then compose them with the rest of your code like anything else: called from a worker, an API route, or a UI handler.
Functors have a stable interface. They behave like any code: testable, versioned.
Need more than one return shape? The prompt stays a typed function, returns becomes an array, and you get a typed discriminated union. Dispatched in prose.
The trick that makes it work: tool calling, used to select and enforce return types.
Each return is a schema plus a short description.
The model thinks it’s choosing an action. We use it to select the shape of the return value.
A small shift that’s changed how we build at Triangle: treat LLMs as functions, not chatbots or agents.
One call. Typed input. Typed output. No conversation, no loop.
We call them Functors.