157 lines
5.5 KiB
TypeScript
157 lines
5.5 KiB
TypeScript
import { NextRequest } from 'next/server'
|
|
|
|
const LLM_API_URL = 'http://api-gateway.api.svc.cluster.local:8080/v1/chat/completions'
|
|
const MODEL = 'reasoning'
|
|
|
|
const SYSTEM_PROMPT = `Poimen. Rock Liang's AI assistant. Answer about technical background, projects, expertise. Specific facts + metrics.
|
|
|
|
## Background
|
|
6+ years Senior Software Engineer. Infrastructure + Backend + LLM Systems. Homelab K8s + vLLM optimization.
|
|
|
|
## Skills
|
|
Infra: Talos Linux K8s (4-node), Terraform GitOps, Cilium eBPF CNI, Longhorn 3-replica, MinIO, PostgreSQL
|
|
Backend: Go, Java, Python, C++. gRPC. Kafka KRaft. Data systems.
|
|
LLM: vLLM 60% latency cut. Model serving. Inference optimization.
|
|
DevOps: ArgoCD. cert-manager. SOPS encryption. AWS CDK/CloudFormation. CloudWatch.
|
|
|
|
## Achievements
|
|
AWS: Distributed-Map 57+ regions, <100ms P99. CDK infrastructure. CF stack mgmt. CloudWatch observability.
|
|
RBC: Terraform deploy 2hr→20min. 99.2% automation.
|
|
Homelab: 99.2% uptime. Production-grade HA.
|
|
|
|
## Operations (Deep)
|
|
Certs: cert-manager + Let's Encrypt. 30d renewal, no downtime. SOPS encrypted secrets. Git audit trail. Prometheus alerts 7d/1d pre-expiry. CertificateTask CRD tracks history.
|
|
|
|
Queues: SQS FIFO + DLQ. Exactly-once via idempotency keys + PostgreSQL. QueueTask CRD. Multi-region failover (SQS-A→B, ordered). Controller detects stalled tasks, exponential backoff. Inference batching by model/token/SLO. Workers scale 1-100.
|
|
|
|
K8s: CRDs + operators. Reconciliation (leader-election, backoff, finalizers). Go controllers (watch/queue/reconcile). API server internals (etcd, versioning, watch). Pod disruption budgets, PreStop hooks. Talos immutable, atomic updates, no SSH, GitOps state. Cilium eBPF policies.
|
|
|
|
Open source: go-flink (distributed DataLakeHouse).
|
|
|
|
## Response Style
|
|
Use **markdown** formatting. Use headers (##), bold (**key terms**), bullet lists, and code blocks where appropriate. Keep answers concise but well-structured. Lead with the direct answer, then supporting details.
|
|
|
|
Example format:
|
|
## Topic
|
|
- **Key fact**: metric or detail
|
|
- **Tech used**: specific tools
|
|
- **Impact**: measurable result
|
|
|
|
Pre-screen context: Technical depth for platform engineer role. Cert rotation, queue semantics, failure modes, scale. Demonstrate production-grade systems.`
|
|
|
|
export async function POST(request: NextRequest) {
|
|
const token = process.env.LLM_API_TOKEN
|
|
|
|
if (!token) {
|
|
return new Response(
|
|
JSON.stringify({ error: 'LLM_API_TOKEN not configured' }),
|
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
)
|
|
}
|
|
|
|
try {
|
|
const { message } = await request.json()
|
|
|
|
const response = await fetch(LLM_API_URL, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
model: MODEL,
|
|
messages: [
|
|
{ role: 'system', content: SYSTEM_PROMPT },
|
|
{ role: 'user', content: message },
|
|
],
|
|
stream: true,
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text()
|
|
throw new Error(`LLM API error: ${response.status} - ${error}`)
|
|
}
|
|
|
|
// Transform the SSE stream
|
|
const encoder = new TextEncoder()
|
|
const decoder = new TextDecoder()
|
|
|
|
const stream = new ReadableStream({
|
|
async start(controller) {
|
|
const reader = response.body?.getReader()
|
|
if (!reader) {
|
|
controller.close()
|
|
return
|
|
}
|
|
|
|
let reasoning = ''
|
|
let content = ''
|
|
|
|
try {
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
const { done, value } = await reader.read()
|
|
if (done) break
|
|
|
|
const chunk = decoder.decode(value, { stream: true })
|
|
const lines = chunk.split('\n')
|
|
|
|
for (const line of lines) {
|
|
if (!line.startsWith('data: ')) continue
|
|
const data = line.slice(6)
|
|
if (data === '[DONE]') continue
|
|
|
|
try {
|
|
const json = JSON.parse(data)
|
|
const delta = json.choices?.[0]?.delta
|
|
|
|
if (delta?.reasoning_content) {
|
|
reasoning += delta.reasoning_content
|
|
controller.enqueue(
|
|
encoder.encode(`data: ${JSON.stringify({ type: 'reasoning', text: reasoning })}\n\n`)
|
|
)
|
|
}
|
|
|
|
if (delta?.content) {
|
|
content += delta.content
|
|
controller.enqueue(
|
|
encoder.encode(`data: ${JSON.stringify({ type: 'content', text: content })}\n\n`)
|
|
)
|
|
}
|
|
|
|
if (json.choices?.[0]?.finish_reason === 'stop') {
|
|
controller.enqueue(
|
|
encoder.encode(`data: ${JSON.stringify({ type: 'done' })}\n\n`)
|
|
)
|
|
}
|
|
} catch {
|
|
// Skip invalid JSON
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
reader.releaseLock()
|
|
controller.close()
|
|
}
|
|
},
|
|
})
|
|
|
|
return new Response(stream, {
|
|
headers: {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
'Connection': 'keep-alive',
|
|
},
|
|
})
|
|
} catch (error) {
|
|
console.error('Chat API error:', error)
|
|
return new Response(
|
|
JSON.stringify({ error: 'Failed to process chat request' }),
|
|
{ status: 500, headers: { 'Content-Type': 'application/json' } }
|
|
)
|
|
}
|
|
}
|
|
// LLM_API_TOKEN now in deployment
|
|
// Image build trigger
|