Este tutorial cria um aplicativo de bate-papo full-stack com React, Node.js e DeepSeek V3 servido por meio da API DeepSeek (api.deepseek.com). No final, você terá um aplicativo funcional que consulta o modelo por meio de um proxy de back-end seguro, com suporte de streaming opcional e orientação sobre como otimizar o uso e os custos do token.
Índice
Por que uma API gerenciada supera a auto-hospedagem para DeepSeek V3
Infraestrutura e comparação de custos
O DeepSeek V3 auto-hospedado requer GPUs A100 ou H100 com VRAM substancial, além da sobrecarga operacional de implantação baseada em Docker, gerenciamento de peso de modelo, fixação de versão e monitoramento de tempo de atividade. Para equipes sem engenheiros de infraestrutura de ML dedicados, isso significa semanas de configuração antes que uma única chamada de API seja realizada.
Um endpoint de API gerenciado elimina toda essa camada. O provedor gerencia endpoints e dimensiona a capacidade. Você paga por token. Os desenvolvedores interagem com o modelo por meio de uma API REST padrão em vez de gerenciar a memória da GPU ou configurações de quantização.
A auto-hospedagem ainda faz sentido em cenários específicos: ambientes isolados com requisitos rígidos de residência de dados, cargas de trabalho onde a taxa de transferência sustentada aumenta o custo da API por token acima da amortização da GPU ou organizações com clusters de GPU e equipes de operações de ML existentes.
Um endpoint de API gerenciado elimina toda essa camada. O provedor gerencia endpoints e dimensiona a capacidade. Você paga por token.
Vantagens da experiência do desenvolvedor
A API DeepSeek segue o formato compatível com OpenAI, portanto a estrutura de solicitação e resposta será familiar para qualquer pessoa que tenha trabalhado com a API OpenAI ou bibliotecas compatíveis. Você ignora downloads de modelos, decisões de quantização (GGUF, GPTQ, AWQ) e configuração manual da janela de contexto no nível da infraestrutura. O provedor lida com o controle de versão do modelo e os endpoints são dimensionados sob carga automaticamente.
Pré-requisitos e configuração da API
O que você precisa
Antes de começar, certifique-se de que o seguinte esteja em vigor:
- Node.js 18.13 ou posterior instalado (para nativo
fetchapoio sem bandeiras; Node.js 21+ recomendado para totalmente estávelfetch) - Uma conta API DeepSeek (inscreva-se em plataforma.deepseek.com)
- Familiaridade básica com APIs REST e padrões de componentes React
- curl (Linux/macOS) ou PowerShell (Windows) para testes de back-end
Criando sua chave de API
Inscreva-se em uma conta API DeepSeek e gere uma chave de API no painel. Armazene a chave de API com segurança e nunca a envie para controle de versão. Adicionar .env para o seu .gitignore arquivo imediatamente:
echo '.env' >> .gitignore
Configure variáveis de ambiente para o projeto em um .env arquivo na raiz do projeto back-end:
DEEPSEEK_API_KEY=your_api_key_here
DEEPSEEK_BASE_URL=https://api.deepseek.com
MODEL_NAME=deepseek-chat
PORT=3001
ALLOWED_ORIGIN=http://localhost:5173
O identificador do modelo API para DeepSeek V3 é deepseek-chat. Você pode verificar os modelos disponíveis ligando para GET /v1/models com sua chave de API. Confirme se o identificador do modelo aparece na resposta antes de continuar.
Construindo o back-end do Node.js
Inicialização e Dependências do Projeto
Crie o diretório do projeto backend, inicialize-o e configure o suporte do módulo ES:
mkdir deepseek-chat-backend && cd deepseek-chat-backend
npm init -y
npm pkg set type=module
npm install express@^4.18.0 cors@^2.8.5 dotenv@^16.0.0
Contexto "type": "module" em package.json é necessário antes de criar server.jsjá que o código usa o módulo ES import sintaxe. O npm pkg set type=module o comando requer npm ≥ 9; alternativamente, adicione manualmente "type": "module" para o seu package.json. O dotenv pacote (a versão 16 ou posterior é necessária para o import 'dotenv/config' sintaxe) carrega variáveis de ambiente do .env arquivo, express fornece a estrutura do servidor HTTP e cors permite solicitações de origem cruzada do frontend React durante o desenvolvimento.
Observe que node-fetch não é necessário no Node.js 18.13 ou posterior, onde fetch está disponível sem sinalizadores. Verifique com node -e 'fetch'. Para estável, não experimental fetchNode.js 21+ é recomendado.
Criando a rota do proxy de API
Solicitações de proxy por meio do back-end por três motivos: mantém a chave de API fora do código do lado do cliente, permite modelagem e validação de solicitações antes de encaminhar para o endpoint do modelo e fornece um local natural para implementar limitação de taxa ou registro.
O back-end expõe um único /api/chat Endpoint POST que recebe mensagens do frontend, constrói uma solicitação para o OpenAI da API DeepSeek compatível /v1/chat/completions endpoint e retorna a resposta do modelo:
import express from 'express';
import cors from 'cors';
import 'dotenv/config';
const app = express();
const {
DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL,
MODEL_NAME,
PORT,
ALLOWED_ORIGIN,
} = process.env;
const REQUIRED_VARS = { DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, MODEL_NAME };
for (const (name, value) of Object.entries(REQUIRED_VARS)) {
if (!value) {
console.error(`Fatal: environment variable ${name} is not set. Exiting.`);
process.exit(1);
}
}
const ALLOWED_BASE_URLS = ('https://api.deepseek.com');
function validateBaseUrl(url) {
const parsed = new URL(url);
if (!ALLOWED_BASE_URLS.includes(parsed.origin)) {
throw new Error(`DEEPSEEK_BASE_URL origin not in allowlist: ${parsed.origin}`);
}
return url;
}
let VALIDATED_BASE_URL;
try {
VALIDATED_BASE_URL = validateBaseUrl(DEEPSEEK_BASE_URL);
} catch (err) {
console.error(`Fatal: ${err.message}`);
process.exit(1);
}
app.use(cors({
origin: ALLOWED_ORIGIN !== undefined ? ALLOWED_ORIGIN : 'http://localhost:5173',
}));
app.use(express.json());
const VALID_ROLES = new Set(('user', 'assistant', 'system'));
const MAX_CONTENT_LENGTH = 32_768;
app.post('/api/chat', async (req, res) => {
const { messages } = req.body;
if (!messages || !Array.isArray(messages)) {
return res.status(400).json({ error: 'messages array is required' });
}
if (messages.length > 50) {
return res.status(400).json({ error: 'Too many messages. Limit to 50.' });
}
for (const msg of messages) {
if (typeof msg.role !== 'string' || !VALID_ROLES.has(msg.role)) {
return res.status(400).json({
error: `Invalid role "${msg.role}". Must be one of: user, assistant, system.`,
});
}
if (typeof msg.content !== 'string') {
return res.status(400).json({ error: 'Each message content must be a string.' });
}
if (msg.content.length > MAX_CONTENT_LENGTH) {
return res.status(400).json({
error: `Message content exceeds maximum length of ${MAX_CONTENT_LENGTH} characters.`,
});
}
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30_000);
try {
let response;
try {
response = await fetch(`${VALIDATED_BASE_URL}/v1/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${DEEPSEEK_API_KEY}`,
},
body: JSON.stringify({
model: MODEL_NAME,
messages,
temperature: 0.7,
max_tokens: 1024,
}),
signal: controller.signal,
});
} finally {
clearTimeout(timeoutId);
}
if (!response.ok) {
const errorBody = await response.text();
console.error('Upstream API error', {
status: response.status,
body: errorBody,
});
return res.status(response.status).json({ error: 'Model API request failed' });
}
const data = await response.json();
res.json(data);
} catch (err) {
console.error('Server error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(PORT || 3001, () => {
console.log(`Backend running on port ${PORT || 3001}`);
});
Testando o ponto final
Antes de construir o frontend, verifique o backend de forma independente.
Linux/macOS (ondulação):
curl -X POST http://localhost:3001/api/chat \
-H "Content-Type: application/json" \
-d '{
"messages": (
{"role": "user", "content": "Explain closures in JavaScript in two sentences."}
)
}'
Windows PowerShell:
Invoke-RestMethod -Method Post -Uri http://localhost:3001/api/chat `
-ContentType 'application/json' `
-Body '{"messages":({"role":"user","content":"Explain closures in JavaScript in two sentences."})}'
Estrutura de resposta esperada:
{
"id": "chatcmpl-...",
"object": "chat.completion",
"choices": ({
"message": {"role": "assistant", "content": "..."},
"finish_reason": "stop"
}),
"usage": {"prompt_tokens": 14, "completion_tokens": 58, "total_tokens": 72}
}
Se você recuperar esse formato, a chave de API, o URL base e o nome do modelo estarão configurados corretamente. Vá para o front-end.
Construindo o front-end do React Chat
Estruturando o aplicativo React
Use Vite para criar o projeto frontend React:
npm create vite@latest deepseek-chat-frontend -- --template react
cd deepseek-chat-frontend && npm install
A estrutura do projeto segue um layout simples: src/App.jsx serve como interface principal de bate-papo. Você pode extrair o componente em src/components/ChatWindow.jsx e src/components/MessageBubble.jsx mais tarde, se o arquivo ficar pesado.
O servidor de desenvolvimento do Vite é executado em http://localhost:5173 por padrão. Esta é a origem configurada no backend ALLOWED_ORIGIN variável de ambiente para CORS.
Implementando a interface de bate-papo
O componente de bate-papo gerencia o histórico de mensagens com useStatelida com a rolagem automática para a mensagem mais recente com useRefe envia a entrada do usuário para o back-end do Node.js no envio do formulário. As mensagens são renderizadas com estilo baseado em função para distinguir a entrada do usuário das respostas do assistente:
import { useState, useRef, useEffect } from 'react';
const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:3001/api/chat';
export default function App() {
const (messages, setMessages) = useState(());
const (input, setInput) = useState('');
const (loading, setLoading) = useState(false);
const (error, setError) = useState(null);
const bottomRef = useRef(null);
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
}, (messages));
const sendMessage = async (e) => {
e.preventDefault();
if (!input.trim() || loading) return;
const userMessage = {
id: `${Date.now()}-user`,
role: 'user',
content: input.trim(),
};
const updatedMessages = (...messages, userMessage);
setMessages(updatedMessages);
setInput('');
setLoading(true);
setError(null);
try {
const res = await fetch(BACKEND_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: updatedMessages.map(({ role, content }) => ({ role, content })),
}),
});
if (!res.ok) throw new Error(`Server responded with ${res.status}`);
const data = await res.json();
const reply = data.choices?.(0)?.message;
if (reply) {
const assistantMessage = {
...reply,
id: `${Date.now()}-assistant`,
};
setMessages((prev) => (...prev, assistantMessage));
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
div style={{ maxWidth: 640, margin: '2rem auto', fontFamily: 'system-ui' }}>
h1>DeepSeek V3 Chat/h1>
div style={{ minHeight: 400, border: '1px solid #ccc', padding: 16, overflowY: 'auto', borderRadius: 8 }}>
{messages.map((msg) => (
div key={msg.id} style={{
textAlign: msg.role === 'user' ? 'right' : 'left',
margin: '8px 0',
}}>
span style={{
display: 'inline-block',
padding: '8px 12px',
borderRadius: 12,
background: msg.role === 'user' ? '#0070f3' : '#f0f0f0',
color: msg.role === 'user' ? '#fff' : '#000',
maxWidth: '80%',
whiteSpace: 'pre-wrap',
}}>
{msg.content}
/span>
/div>
))}
{loading && div style={{ color: '#888' }}>Thinking.../div>}
{error && div style={{ color: 'red' }}>Error: {error}/div>}
div ref={bottomRef} />
/div>
form onSubmit={sendMessage} style={{ display: 'flex', marginTop: 12, gap: 8 }}>
input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask DeepSeek V3 something..."
style={{ flex: 1, padding: 10, borderRadius: 6, border: '1px solid #ccc' }}
/>
button type="submit" disabled={loading} style={{ padding: '10px 20px', borderRadius: 6 }}>
Send
/button>
/form>
/div>
);
}
Para compilações de produção, defina o VITE_BACKEND_URL variável de ambiente em um .env arquivo na raiz do projeto frontend (por exemplo, VITE_BACKEND_URL=https://your-backend.example.com/api/chat).
Tratamento de respostas de streaming (aprimoramento opcional)
A API DeepSeek oferece suporte a respostas de streaming. Para habilitar o streaming, o back-end canaliza o fluxo de resposta bruto para o cliente e o front-end o consome com o ReadableStream API.
Observação: Os trechos a seguir são ilustrativos e necessitam de adaptação para uma implementação completa. O streaming completo requer análise adequada de pedaços SSE no frontend. Consulte a documentação da API DeepSeek para obter o formato exato de resposta de streaming.
Modificação de back-end — substitua o tratamento de resposta sem streaming dentro do /api/chat rota:
import { Readable } from 'stream';
body: JSON.stringify({ model: MODEL_NAME, messages, stream: true }),
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const nodeReadable = Readable.fromWeb(response.body);
nodeReadable.pipe(res);
nodeReadable.on('error', (err) => {
console.error('Stream error:', err);
res.end();
});
Modificação de front-end – em sendMessage()substitua o res.json() ligue com um leitor de streaming:
const reader = res.body.getReader();
const decoder = new TextDecoder();
let accumulated = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
accumulated += chunk;
const lines = accumulated.split('
');
accumulated = lines.pop() || '';
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || !trimmed.startsWith('data: ')) continue;
const payload = trimmed.slice(6);
if (payload === '(DONE)') break;
}
}
Com o streaming habilitado, os tokens aparecem na IU à medida que o modelo os gera, e não após a conclusão da resposta completa. A latência percebida cai substancialmente para respostas mais longas.
Com o streaming habilitado, os tokens aparecem na IU à medida que o modelo os gera, e não após a conclusão da resposta completa. A latência percebida cai substancialmente para respostas mais longas.
Otimizando suas solicitações do DeepSeek V3
Dicas imediatas de engenharia
O DeepSeek V3 responde bem aos prompts estruturados do sistema que atribuem uma função clara e definem restrições comportamentais explícitas. Em vez de instruções vagas como “seja útil”, forneça orientações concretas: especifique o formato de saída, defina a persona e restrinja o escopo. Para tarefas de geração de código, comece com um temperature de 0,2 ou 0,3 para reduzir a variação de saída em prompts idênticos. Para a escrita criativa, valores em torno de 0,8 a 1,0 permitem maior variabilidade. Para perguntas e respostas factuais, comece com um temperature de 0,3 a 0,5 e um top_p de 0,9 e ajuste com base em seus requisitos de consistência. Consulte o cartão do modelo DeepSeek para recomendações específicas do modelo.
Gerenciando uso e custos de token
O preço baseado em token significa que o controle do consumo de token afeta diretamente o custo. Definir max_tokens ao mínimo necessário para o comprimento de resposta esperado. Implemente o truncamento de mensagens do lado do cliente para evitar que a janela de contexto da conversa cresça ilimitadamente. Uma abordagem prática: limite o histórico de mensagens enviadas à API às N mensagens mais recentes.
{
"model": "deepseek-chat",
"messages": (
{
"role": "system",
"content": "You are a senior JavaScript developer. Provide concise, production-ready code with brief explanations. Use ES module syntax."
},
...conversationHistory.slice(-10)
),
"temperature": 0.3,
"top_p": 0.9,
"max_tokens": 512
}
Essa solicitação combina três estratégias de controle de custos: um prompt de sistema focado que reduz saídas desnecessárias, um histórico de mensagens truncado e um procedimento conservador. max_tokens valor.
Armadilhas comuns e solução de problemas
Erros de autenticação e rede
Uma resposta 401 da API DeepSeek significa que a autenticação falhou. Você enviou uma chave de API ausente, malformada ou revogada. Um 403 significa que a chave é válida, mas não possui as permissões necessárias. Verifique a chave em seu .env arquivo, confirme dotenv carrega antes que a chave seja acessada e verifique se a chave foi revogada no painel da API.
Erros de tempo limite podem ocorrer durante períodos de alta demanda. Lide com eles implementando um mecanismo de nova tentativa com um limite de tempo limite razoável no proxy de back-end.
Disponibilidade do modelo e limites de taxas
A API DeepSeek impõe limites de taxa que variam de acordo com o nível da conta. Verifique o Documentação de limite de taxa DeepSeek para os limites específicos do seu nível. Quando você excede o limite, a API retorna um código de status 429. A mitigação padrão é a espera exponencial: tente novamente a solicitação após um atraso crescente (por exemplo, 1 segundo, depois 2, depois 4, até um máximo configurável). Registre eventos de limite de taxa para monitorar se o aplicativo atinge consistentemente os limites, o que pode indicar a necessidade de um plano de nível superior ou de solicitação em lote.
O preço baseado em token significa que o controle do consumo de token afeta diretamente o custo. Definir
max_tokensao mínimo necessário para o comprimento de resposta esperado.
Lista de verificação de implementação
Referência rápida: lista de verificação de configuração completa
- ☐ Crie uma conta API DeepSeek e gere uma chave API
- ☐ Defina variáveis de ambiente (
DEEPSEEK_API_KEY,DEEPSEEK_BASE_URL,MODEL_NAME=deepseek-chat,ALLOWED_ORIGIN) - ☐ Adicionar
.envpara.gitignore - ☐ Inicialize o projeto Node.js, defina
"type": "module"e instale dependências fixadas (express@^4.18.0,cors@^2.8.5,dotenv@^16.0.0) - ☐ Crie um proxy Express com
/api/chatendpoint e CORS restrito à origem - ☐ Verifique o back-end com
curl(Linux/macOS) ouInvoke-RestMethod(Windows) - ☐ Aplicativo Scaffold React com Vite
- ☐ Implementar UI de bate-papo com estado de mensagem e lógica de busca
- ☐ (Opcional) Adicionar suporte para resposta de streaming
- ☐ Ajuste o prompt do sistema, a temperatura e
max_tokens - ☐ Implementar tratamento de erros e lógica de repetição de limite de taxa
- ☐ Implantar back-end e front-end — atualização
ALLOWED_ORIGINpara o URL do front-end de produção, definaVITE_BACKEND_URLao seu URL de back-end de produção e injete variáveis de ambiente por meio do gerenciador de segredos da sua plataforma
Próximas etapas
Este tutorial produziu um aplicativo de bate-papo full-stack funcional desenvolvido com DeepSeek V3 por meio da API DeepSeek, sem necessidade de infraestrutura de GPU. As extensões naturais incluem adicionar persistência de conversação com uma camada de banco de dados, implementar geração aumentada de recuperação (RAG) usando um modelo de incorporação ou experimentar outros modelos disponíveis na plataforma. O Documentação da API DeepSeek fornece mais detalhes sobre parâmetros disponíveis, recursos de modelo e opções de configuração avançada.
Source link




