Curriculum / AI Engineer — AIP Builder
Author an LLM tool in code: TypeScript Functions + structured output
Introduction
Scenario: The disruption decision-support team needs a fast, deterministic severity label on every incoming supply-chain DisruptionEvent, and the no-code Use LLM block alone cannot give them the typed, testable, reusable classifier they want to drop into the recommendation flow. You will author that classifier in code as a published TypeScript Function so the rest of the workflow can call it as a governed tool.
In the previous units you built and grounded a no-code AIP Logic function with the Use LLM block. This unit moves the work into code: AIP for developers lets you call a Palantir-provided (or registered/BYOM) language model directly inside a TypeScript Function via generated model bindings, so a single model call becomes a typed, versioned, reusable unit of logic. You will author classifyDisruptionSeverity, a function that takes a DisruptionEvent and returns a structured result with a severity label and a short rationale, then publish it so the rest of the decision-support workflow can call it like any other Foundry function.
The mechanics matter because they are the seam between AIP and ordinary code. Inside a TypeScript v1 function you import a model through Model Imports (Add > Models), which generates code bindings; you then call createChatCompletion({ messages, params }) and handle the discriminated-union response, where response.type === 'ok' carries the completion and any other branch is an error you must handle explicitly rather than assume away. To make the output dependable for downstream callers, you constrain the model to emit a structured shape (a severity enum plus a rationale string) and parse it into a typed return value, so the function's contract is the typed result, not free-form prose.
Publishing is what turns this code into a platform citizen. A published function is enumerable as a query type and executable by apiName, can back Actions, and can be imported into an AIP Logic Use LLM block as a Call function tool, which is exactly how this classifier rejoins the recommendation flow. Be precise about what is and is not externally observable here: a function that has only been saved (not published) has no queryApiName and cannot be reached by Execute Query, and the function's source, prompt, and model binding are never exposed by a read API. We therefore confirm this work by executing the published function and inspecting its output shape, and we self-attest the parts (the createChatCompletion call, the Logic tool wiring) that no read API can prove.
Capability focus: TypeScript Functions calling language models (createChatCompletion); structured/typed output; publishing as a query function. · Artifact: A published TypeScript Function (classifyDisruptionSeverity) returning a structured result, importable as a Logic Call function tool.
Key concepts
- Language models in TypeScript v1 functions: You use an LLM inside a TypeScript function by importing a model through Model Imports (Add > Models), which generates code bindings (e.g. Gpt41). You call the binding's createChatCompletion({ messages, params }) to get a completion. The same import mechanism exposes registered (BYOM) models, so the code path is identical whether the model is Palantir-provided or your own.
- Discriminated-union response handling: createChatCompletion returns a discriminated union, not a bare string. You branch on response.type — the 'ok' branch carries the model output, and other branches represent errors (refusals, timeouts, filtering). Robust functions handle the non-'ok' branches explicitly instead of assuming success, which is what makes the function safe to call from Actions and Logic.
- Structured / typed output: Rather than returning prose, the function constrains the model to a structured shape and parses it into a typed return value — here a severity classification plus a rationale. Registered models and Palantir-provided models support structured outputs, so the function's contract becomes a typed object that downstream callers (Logic, Actions, Evals) can rely on by key.
- Publishing a function as a query type: Selecting Publish (next to Save) makes the function enumerable via List Query Types and executable via the Execute Query API by its apiName. Only the published function has a queryApiName; a merely-saved function is not addressable by Execute Query. Once published, the function can back Actions, be called from Workshop and other Logic functions, and be imported as a Logic Call function tool.
- Call function tool in AIP Logic: The Use LLM block's Ontology-driven tools include Query objects, Call function, Apply actions, and Calculator. Call function can invoke functions defined in repositories or existing Logic functions, which is how the published classifyDisruptionSeverity is wired back into the recommendation flow as a tool the model can call.
- Supported LLMs and AIP permissions: AIP supports models from providers including xAI, OpenAI, Anthropic, Meta, and Google; using Palantir-provided models requires AIP enabled on the enrollment and AIP builder permissions. The model you import in code must be one your enrollment is entitled to use.
- Execute Query confirms behavior, not internals: Execute Query (POST /api/v2/functions/queries/{queryApiName}/execute, preview=true) runs the latest published version of a query by apiName with a parameters map and returns the result as JSON. It proves the function executes and returns the declared output shape; it never exposes the function's source, prompt, or model binding.
Companion video
Functions + language models walkthrough (placeholder) · open on YouTube
Hands-on activity
each step validates · the unit completes when all steps pass- 1
Code-authored function is published and executable
This is where the code-authored classifier proves itself by running. Author classifyDisruptionSeverity as a TypeScript v1 function that takes a disruptionId, resolves the DisruptionEvent, calls your imported model's createChatCompletion, and returns a structured result with a severity label and a rationale. Publish it (Publish, next to Save) so it gains a queryApiName and becomes addressable. The check executes the published function via the Execute Query API (POST /api/v2/functions/queries/{queryApiName}/execute, preview=true — a preview API, flagged) on the seeded disruption DISR-CAP-1 and confirms the returned JSON is a structured, non-empty object carrying the keys 'severity' and 'rationale'. Note what this does and does not verify: it confirms the published function executes and emits the declared output shape; it cannot and does not inspect your source, prompt, or model choice. A function that is only saved and not published has no queryApiName and would not be reachable here at all.
not startedinstance checkExecutes the published classifyDisruptionSeverity on a seeded input and confirms a structured, non-empty result.
- 2
Function is enumerable as a published query type
Publishing also makes the function discoverable, which is a separate, weaker claim than 'it runs correctly' and worth confirming on its own. The check calls List Query Types (GET /api/v2/ontologies/{ontology}/query-types) and confirms classifyDisruptionSeverity's apiName appears in the enumerated published query types, establishing existence and a callable signature. This is the API-level evidence that you completed the Publish step rather than leaving the function saved-only: only published functions are enumerated here and only published functions carry a queryApiName. If the apiName is absent, the function was never published (or was published under a different name) — re-publish from the next-to-Save Publish control and confirm the apiName matches what downstream callers expect.
not startedinstance checkConfirms classifyDisruptionSeverity appears in the list of published query types.
- 3
Model binding uses createChatCompletion and handles the union response
This step is self-attested because no read API exposes a function's source code. Inside the function, import a model through Model Imports (Add > Models) so Foundry generates the code bindings, then call the binding's createChatCompletion({ messages, params }) and handle the discriminated-union response by branching on response.type: read the completion only on the 'ok' branch, and handle every other branch (errors, refusals, filtering) explicitly rather than assuming success. The execution checks in the earlier steps prove the function runs and returns the right shape, but they cannot see that you used createChatCompletion or that you handled the non-'ok' branches — that is precisely why this is a manual attestation. Confirm in the code editor that the import exists and that response.type === 'ok' is checked before the output is parsed.
not startedself-attestedSelf-attested: the function imports a model and calls createChatCompletion, handling response.type==='ok'.
- 4
Function is wired as a Call function tool in Logic
This step is self-attested because the configuration inside an AIP Logic function — its blocks, prompt, output schema, and tool wiring — is not inspectable by any read API (R-VAL1). In the recommendation Logic function's Use LLM block, add the published classifyDisruptionSeverity through the Call function tool so the model can invoke your typed classifier as part of grounding its recommendation. Because the Use LLM block's internals are opaque to the platform APIs, no automated check can confirm the tool is wired; verify it manually in the Logic editor by opening the Use LLM block, confirming classifyDisruptionSeverity is listed under the Call function tool, and running the block on DISR-CAP-1 to see the tool invoked. The Execute Query checks confirm the classifier itself is runnable, which is the prerequisite that makes this wiring meaningful.
not startedself-attestedSelf-attested: the Logic function's Use LLM block adds the published function via the Call function tool.

