기존 함수 기반 모델 사용하기
ChatCompletion 함수 인터페이스로 등록하는 기존 방식은 TypeScript v1 전용이며, 같은 등록 방식의 TypeScript v2 구현은 없습니다. 2026년 3월부터 권장되는 현재 등록 모델 방식은 지원 애플리케이션의 모델 선택기에 자동으로 연결되므로 별도의 함수 연동 절차가 필요하지 않습니다.
AIP Logic
Use LLM 보드에서 모델 목록을 열고 Registered 탭을 선택한 뒤 게시한 함수를 고릅니다.
Pipeline Builder
기존 함수 기반 LLM의 Pipeline Builder 연동은 베타입니다. 인스턴스에 제공되지 않을 수 있고 개발 중 동작이 바뀔 수 있습니다.
Use LLM 변환의 Model → Show configurations를 열고 Model type → Registered에서 함수를 선택합니다.
웹훅 대신 직접 소스 호출
Pipeline Builder의 이 BYOM 경로는 웹훅을 지원하지 않습니다. REST API 소스는 구성하되 웹훅은 만들지 않고, TypeScript 함수에서 소스의 HTTP 클라이언트로 외부 모델에 직접 요청합니다.
소스 설정에 따라 외부 통신과 내보내기를 허용하고, 코드 저장소와 파이프라인에서 소스를 가져올 수 있게 설정합니다. 그런 다음 저장소에 소스와 ChatCompletion 인터페이스를 가져옵니다.
함수에는 @ChatCompletion()과 @ExternalSystems({ sources: [...] })를 모두 선언합니다. getHttpsConnection().url로 기본 주소를, getSecret(...)으로 소스에 저장한 비밀정보를 읽습니다.
소스의 fetch를 사용해야 인증서와 외부 통신 프록시 구성이 실행 환경에 맞게 적용됩니다. 전역 fetch를 그대로 쓰는 방식은 모든 함수 실행 환경에서 외부 연결을 보장하지 않습니다.
처리량 제한 오류를 호출자에게 전달
함수 반환형에 ChatCompletionError를 선언하고 FunctionsResult로 결과를 반환하면 AIP Logic과 Pipeline Builder가 처리량 제한 오류를 인식할 수 있습니다.
FunctionsResult.err("LanguageModel:RateLimitExceeded", {
retryAfterMillis: 20000,
});
retryAfterMillis의 단위는 밀리초입니다. 이 예시의 20,000은 재시도 전에 20초 기다리라는 뜻입니다. AIP Logic과 Pipeline Builder는 지연 시간을 점차 늘리는 지수 백오프와 무작위 지연을 결합하여 반복적인 한도 충돌과 동시 재시도 집중을 줄입니다.
HTTP 429를 무조건 재시도하지 않기
외부 제공자의 HTTP 상태와 오류 코드를 함께 확인해야 합니다. OpenAI의 일시적인 처리량 제한과 청구 또는 사용 한도 문제는 같은 HTTP 429를 사용할 수 있습니다.
| 응답 | 처리 |
|---|---|
429의 rate_limit_exceeded, slow_down | 처리량 제한으로 전달하여 호출자가 재시도하도록 구성 가능 |
| 청구, 크레딧 또는 사용 한도 오류 | 계정이나 한도에 대한 조치가 필요. 반복 요청만으로 해결되지 않음 |
| 그 밖의 오류 | 정상 응답이나 처리량 제한으로 위장하지 않고 실패로 처리 |
아래 구현은 알려진 두 처리량 제한 코드만 재시도 가능한 오류로 전달합니다. Retry-After가 있으면 그 지연을 사용하고, 없거나 해석할 수 없으면 예시 기본값인 20초를 사용합니다.
직접 호출 구현 예시
다음은 소스의 API 이름이 OpenAI이고, 추가 비밀정보의 코드 식별자가 additionalSecretAPIKey인 경우입니다. 소스에 정의된 실제 API 이름과 비밀정보 식별자를 사용해야 합니다.
메시지의 SYSTEM, USER, ASSISTANT 역할을 OpenAI 형식으로 변환하여 gpt-4o에 전달합니다. params는 인터페이스 계약을 위해 유지하지만 이 메시지 기반 예시에서는 추가 생성 설정을 요청에 전달하지 않습니다.
import { ExternalSystems, FunctionsResult, UserFacingError } from "@foundry/functions-api";
import { OpenAI } from "@foundry/external-systems/sources";
import { ChatCompletion, ChatCompletionError } from "@palantir/languagemodelservice/contracts/ChatCompletion";
import {
ChatMessageRole,
FunctionsGenericChatCompletionRequestMessages,
FunctionsGenericChatCompletionResponse,
GenericCompletionParams,
} from "@palantir/languagemodelservice/api";
export class MyFunctions {
@ChatCompletion()
@ExternalSystems({ sources: [OpenAI] })
public async myChatCompletion(
messages: FunctionsGenericChatCompletionRequestMessages,
params: GenericCompletionParams,
): Promise<FunctionsResult<FunctionsGenericChatCompletionResponse, ChatCompletionError>> {
const apiKey = OpenAI.getSecret("additionalSecretAPIKey");
const baseUrl = OpenAI.getHttpsConnection().url;
const response = await OpenAI.fetch(`${baseUrl}/v1/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-4o",
messages: messages.map(message => ({
role: convertRole(message.role),
content: message.content,
})),
}),
});
if (!response.ok) {
if (response.status === 429) {
const body = await response.json();
const code = body?.error?.code;
if (code === "rate_limit_exceeded" || code === "slow_down") {
return FunctionsResult.err("LanguageModel:RateLimitExceeded", {
retryAfterMillis: retryDelay(response.headers.get("Retry-After")),
});
}
}
throw new UserFacingError(`외부 모델 호출 실패: HTTP ${response.status}`);
}
const body = await response.json();
const completion = body?.choices?.[0]?.message?.content;
const usage = body?.usage;
if (typeof completion !== "string" || !usage ||
![usage.prompt_tokens, usage.completion_tokens, usage.total_tokens]
.every(value => Number.isSafeInteger(value) && value >= 0)) {
throw new UserFacingError("모델 응답의 텍스트 또는 토큰 사용량이 올바르지 않습니다.");
}
return FunctionsResult.ok({
completion,
tokenUsage: {
promptTokens: usage.prompt_tokens,
completionTokens: usage.completion_tokens,
maxTokens: usage.total_tokens,
},
});
}
}
function convertRole(role: ChatMessageRole): "system" | "user" | "assistant" {
switch (role) {
case "SYSTEM": return "system";
case "USER": return "user";
case "ASSISTANT": return "assistant";
default: throw new UserFacingError(`지원하지 않는 메시지 역할: ${role}`);
}
}
function retryDelay(header: string | null): number {
if (header === null || header.trim() === "") return 20000;
const seconds = Number(header);
if (Number.isFinite(seconds)) {
const millis = Math.ceil(seconds * 1000);
return seconds >= 0 && Number.isSafeInteger(millis) ? millis : 20000;
}
const date = Date.parse(header);
return Number.isFinite(date) ? Math.max(0, date - Date.now()) : 20000;
}
직접 HTTP 호출의 JSON에서는 choices와 usage를 바로 읽습니다. 웹훅 결과에 사용하는 value.output 구조는 없습니다. 비밀정보나 전체 오류 응답을 로그에 출력하지 않습니다.
텍스트나 토큰 사용량이 누락되면 호출자에게 오류를 반환합니다.
게시와 사용
- 라이브 미리보기에서 함수를 시험합니다. 실제 외부 모델 호출과 비용이 발생할 수 있습니다.
- 버전을 태그하여 게시합니다.
- Pipeline Builder에서 파이프라인을 만듭니다.
- Use LLM의 사용자 등록 모델 목록에서 해당 함수를 선택합니다.