Documents
Home>Documents>AI>Agent>Xgen

Designing a Universal LLM Node for ChatOpenAI and ChatAnthropic

6 min readFeb 20, 2025Feb 22, 2026

Designing a General-Purpose LLM Node

The LLM node is the most critical building block in the XGen canvas. It needed to handle ChatOpenAI, ChatAnthropic, and internal models through a single unified interface. This post covers how we designed that integration.

Defining a Common Interface

We defined a base interface shared by all LLM nodes.

interface LLMNodeData {
  provider: 'openai' | 'anthropic' | 'custom';
  model: string;
  temperature: number;
  maxTokens: number;
  systemPrompt: string;
  topP?: number;
  frequencyPenalty?: number;
  presencePenalty?: number;
}

// 모델 목록 관리
const MODEL_OPTIONS: Record<string, string[]> = {
  openai: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'o1-preview'],
  anthropic: ['claude-sonnet-4-20250514', 'claude-3.5-sonnet', 'claude-3-haiku'],
  custom: [], // 동적으로 로드
};

Building the ChatOpenAI Node UI

Each LLM node handles model selection, parameter tuning, and prompt input all within the node itself.

const ChatOpenAINode: React.FC<NodeProps<LLMNodeData>> = ({ data, id }) => {
  const updateNodeData = useCanvasStore((s) => s.updateNodeData);
  
  return (
    <BaseNode title="ChatOpenAI" icon={<OpenAIIcon />} color="green">
      <Handle type="target" position={Position.Left} id="input" />
      
      <NodeSelect
        label="Model"
        value={data.model}
        options={MODEL_OPTIONS.openai}
        onChange={(v) => updateNodeData(id, { model: v })}
      />
      <NodeSlider
        label="Temperature"
        value={data.temperature}
        min={0} max={2} step={0.1}
        onChange={(v) => updateNodeData(id, { temperature: v })}
      />
      <NodeTextarea
        label="System Prompt"
        value={data.systemPrompt}
        onChange={(v) => updateNodeData(id, { systemPrompt: v })}
      />
      
      <Handle type="source" position={Position.Right} id="output" />
    </BaseNode>
  );
};

What's Different About the ChatAnthropic Node

Anthropic's parameter surface differs slightly from OpenAI's — most notably, it adds a top_k parameter and handles system prompts differently. We surfaced these differences naturally in the node UI rather than forcing a one-size-fits-all layout.

Prompt Template System

We added a feature for saving frequently used system prompts as templates and loading them on demand. Templates support variable substitution, so you can use placeholders like {{context}} or {{question}}.

interface PromptTemplate {
  id: string;
  name: string;
  content: string;
  variables: string[];
}

function resolveTemplate(template: string, vars: Record<string, string>) {
  return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] || '');
}

Key Takeaway

Each LLM provider returns responses in subtly different formats. If you want to present them uniformly on the frontend, a normalization layer in between is non-negotiable. Standardizing the response format at the backend API design stage pays off significantly later — it cuts a large amount of frontend work down the road.

Tags
LLMChatOpenAIChatAnthropicnode designprompt