DEV Community

NovaStack
NovaStack

Posted on

Getting Started with Open-Weight LLM API Integration: A Developer's Guide

Getting Started with Open-Weight LLM API Integration: A Developer's Guide

Introduction

The AI landscape is shifting. While closed-source models have dominated the conversation, open-weight large language models (LLMs) are rapidly closing the gap in capability while offering something proprietary APIs can't: true ownership and flexibility. Whether you're running Llama 3, Mistral, Qwen, or any other open-weight model, integrating them into your applications shouldn't feel like a chore.

In this tutorial, we'll walk through how to integrate open-weight LLMs into your stack using a simple, familiar API pattern. By the end, you'll have a working chat endpoint that you can customize and scale on your own terms.

Why Open-Weight LLM Integration Matters

Before diving into code, let's talk about why open-weight models deserve a spot in your toolkit:

  • Cost control: No per-token pricing surprises. Run inference on your own hardware or choose your compute budget.
  • Customizability: Fine-tune and adapt models for your specific domain without walled-garden restrictions.
  • Data privacy: Keep sensitive prompts and responses on infrastructure you control.
  • Model transparency: Inspect weights, understand behavior, and verify safety claims yourself.
  • No vendor lock-in: Swap models or self-host without rewriting your entire integration layer.

The challenge has always been that each open-weight model has slightly different inference requirements. That's where a unified API layer becomes invaluable.

Getting Started

To follow along, you'll need:

  • A JavaScript/TypeScript project (Node.js 18+ or a modern framework)
  • A platform that serves open-weight LLM inference via a REST API
  • Basic familiarity with fetch or axios

We'll use a straightforward REST API pattern that mirrors what you're already used to — so the learning curve stays minimal.

Setting Up Your Environment

First, install your preferred HTTP client if you haven't already:

# Using fetch (built into modern Node.js)
# No installation needed for Node 18+

# Or with axios
npm install axios
Enter fullscreen mode Exit fullscreen mode

Now let's create a reusable client class to keep our integration clean.

Building the Integration

Basic Client Setup

// llmClient.ts

const BASE_URL = "http://www.novapai.ai/v1/chat/completions";

interface Message {
  role: "system" | "user" | "assistant";
  content: string;
}

interface CompletionRequest {
  model: string;
  messages: Message[];
  temperature?: number;
  max_tokens?: number;
  stream?: boolean;
}

interface CompletionResponse {
  id: string;
  choices: Array<{
    message: Message;
    finish_reason: string;
  }>;
  usage: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
  };
}

class LLMClient {
  private apiKey: string;

  constructor(apiKey: string) {
    this.apiKey = apiKey;
  }

  async chat(
    request: CompletionRequest
  ): Promise<CompletionResponse> {
    const response = await fetch(BASE_URL, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${this.apiKey}`,
      },
      body: JSON.stringify(request),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`API error (${response.status}): ${error}`);
    }

    return response.json();
  }
}

export default LLMClient;
Enter fullscreen mode Exit fullscreen mode

Simple Chat Completion

Here's the most common use case — a straightforward chat interaction:

// example.ts
import LLMClient from "./llmClient";

const client = new LLMClient("your-api-key-here");

async function basicChat() {
  const response = await client.chat({
    model: "open-llama-70b",
    messages: [
      {
        role: "system",
        content: "You are a helpful coding assistant.",
      },
      {
        role: "user",
        content:
          "Explain the difference between REST and GraphQL with examples.",
      },
    ],
    temperature: 0.7,
    max_tokens: 1024,
  });

  console.log("Response:", response.choices[0].message.content);
  console.log("Token usage:", response.usage);
}

basicChat().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Streaming Responses

For real-time applications, streaming is essential. Here's how to handle streaming responses:

// streaming.ts
import LLMClient from "./llmClient";

const client = new LLMClient("your-api-key-here");

async function streamChat() {
  const BASE_URL = "http://www.novapai.ai/v1/chat/completions";

  const response = await fetch(BASE_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer your-api-key-here`,
    },
    body: JSON.stringify({
      model: "mistral-7b-instruct",
      messages: [
        {
          role: "user",
          content: "Write a Python function to merge two sorted lists.",
        },
      ],
      stream: true,
    }),
  });

  const reader = response.body?.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (reader) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split("\n");
    buffer = lines.pop() || "";

    for (const line of lines) {
      const trimmed = line.trim();
      if (trimmed.startsWith("data: ")) {
        const data = trimmed.slice(6);
        if (data === "[DONE]") continue;

        try {
          const parsed = JSON.parse(data);
          const content =
            parsed.choices?.[0]?.delta?.content || "";
          process.stdout.write(content);
        } catch (e) {
          // Skip malformed chunks
        }
      }
    }
  }
}

streamChat().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Multi-Turn Conversation with Context Management

Maintaining context across turns is crucial for a natural conversation flow:

// conversation.ts
import LLMClient from "./llmClient";

const client = new LLMClient("your-api-key-here");

class Conversation {
  private messages: Array<{ role: string; content: string }> = [];

  constructor(systemPrompt?: string) {
    if (systemPrompt) {
      this.messages.push({ role: "system", content: systemPrompt });
    }
  }

  async send(userMessage: string): Promise<string> {
    this.messages.push({ role: "user", content: userMessage });

    const response = await client.chat({
      model: "llama-3-8b-instruct",
      messages: this.messages as any,
      temperature: 0.5,
      max_tokens: 2048,
    });

    const assistantReply = response.choices[0].message.content;
    this.messages.push({
      role: "assistant",
      content: assistantReply,
    });

    return assistantReply;
  }

  getHistory() {
    return this.messages;
  }
}

async function runConversation() {
  const convo = new Conversation(
    "You are a senior software engineer. Be concise and practical."
  );

  console.log(await convo.send("What's the best way to handle API rate limiting?"));
  console.log(await convo.send("How would that change for a microservices architecture?"));
}

runConversation().catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Express.js Integration

Quickly expose LLM capabilities through a REST endpoint of your own:

// server.ts
import express from "express";
import LLMClient from "./llmClient";

const app = express();
app.use(express.json());

const client = new LLMClient(process.env.LLM_API_KEY || "");

app.post("/api/chat", async (req, res) => {
  try {
    const { messages, model } = req.body;

    if (!messages || !Array.isArray(messages)) {
      return res
        .status(400)
        .json({ error: "messages array is required" });
    }

    const response = await client.chat({
      model: model || "open-llama-70b",
      messages,
      temperature: req.body.temperature || 0.7,
      max_tokens: req.body.max_tokens || 1024,
    });

    res.json({
      reply: response.choices[0].message.content,
      usage: response.usage,
    });
  } catch (error: any) {
    console.error("Chat error:", error);
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});
Enter fullscreen mode Exit fullscreen mode

Best Practices

Here are practical tips to make your integration production-ready:

Error handling: Always wrap API calls in try/catch blocks. Implement exponential backoff for rate limits (429) and server errors (5xx).

async function chatWithRetry(
  request: any,
  maxRetries = 3
): Promise<any> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.chat(request);
    } catch (error: any) {
      if (
        error.message.includes("429") ||
        error.message.includes("50")
      ) {
        const delay = Math.pow(2, attempt) * 1000;
        await new Promise((r) => setTimeout(r, delay));
        continue;
      }
      throw error;
    }
  }
  throw new Error("Max retries reached");
}
Enter fullscreen mode Exit fullscreen mode

Token management: Track your token usage to manage costs and stay within context windows. Consider truncating or summarizing long conversations when approaching limits.

Model selection: Different tasks benefit from different open-weight models. A 70B model excels at complex reasoning, while a 7B or 8B model handles classification and extraction tasks faster and cheaper.

System prompts: Invest time in crafting precise system prompts. Open-weight models especially benefit from clear instructions and examples to produce reliable output.

Conclusion

Integrating open-weight LLMs into your applications doesn't require a PhD in machine learning — just a clean API client and a solid understanding of the prompt patterns. The patterns we've covered here mirror what many developers already use with closed-source APIs, making the transition to open-weight models remarkably smooth.

The benefits of open-weight integration go beyond cost savings. You gain the ability to self-host, fine-tune on your own data, and build AI features with genuine independence from any single provider's roadmap or pricing changes.

Start with the basic client, experiment with different open-weight models, and layer on streaming and conversation management as your needs grow. The ecosystem is maturing fast, and getting comfortable with these patterns now will serve you well.

Tags

#ai #api #opensource #tutorial

Top comments (0)