Wir haben LangChain als Konsumenten betrachtet – ein System, das APIs aufruft, Datenbanken abfragt, Vector Stores durchsucht. Doch was, wenn LangChain selbst die API ist? Was, wenn andere Services, andere Anwendungen, andere Teams die LLM-Fähigkeiten nutzen wollen, ohne sich mit Prompts, Chains oder Tools auseinandersetzen zu müssen?
Das ist die Microservice-Perspektive. Ein dedizierter Service, der LLM-Logik kapselt und über HTTP-Endpunkte verfügbar macht. Frontend-Entwickler rufen eine REST-API auf. Mobile Apps konsumieren JSON-Responses. Andere Backend-Services integrieren KI-Funktionalität über standardisierte Schnittstellen. Die LangChain-Komplexität bleibt verborgen hinter einer klaren API-Konvention.
Warum ist das wertvoll? Separation of Concerns. Das LLM-Team kann unabhängig arbeiten, Prompts optimieren, Modelle wechseln, Retrieval-Strategien verfeinern – ohne dass andere Teams ihre Integrationen anpassen müssen. Das API-Interface bleibt stabil. Die Implementierung darunter kann evolvieren. Das ist gute Architektur.
Hinzu kommt: Skalierung. Ein LLM-Service kann horizontal skaliert werden. Load Balancer verteilen Requests auf mehrere Instanzen. Rate Limiting schützt vor Überlastung. Monitoring und Logging werden zentral gehandhabt. Authentifizierung und Autorisierung werden einheitlich implementiert. All das sind Probleme, die man einmal löst, nicht in jeder Anwendung neu.
Flask ist minimalistisch. Es bietet HTTP-Routing, Request-Handling und Response-Serialisierung – mehr nicht. Für einfache, synchrone LLM-Services reicht das völlig. Wir nehmen den gegebenen Code als Ausgangspunkt und wrappen ihn in einen HTTP-Endpunkt.
from flask import Flask, request, jsonify
from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field
from typing import Optional
import logging
# Logging konfigurieren
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Flask-App initialisieren
app = Flask(__name__)
# LLM einmal beim Start initialisieren (nicht pro Request!)
# Das spart Zeit und Ressourcen
llm = init_chat_model("gpt-4o-mini", temperature=0.7)
# Pydantic-Model für strukturierte Antwort
class LLMResponse(BaseModel):
"""Strukturierte Antwort des LLM-Services."""
question: str = Field(description="Die ursprüngliche Frage")
answer: str = Field(description="Die generierte Antwort")
model: str = Field(description="Das verwendete Modell")
tokens_used: Optional[int] = Field(
default=None,
description="Anzahl verwendeter Tokens (wenn verfügbar)"
)
class ErrorResponse(BaseModel):
"""Strukturierte Fehlerantwort."""
error: str = Field(description="Fehlerbeschreibung")
status_code: int = Field(description="HTTP-Statuscode")
@app.route('/health', methods=['GET'])
def health_check():
"""
Health-Check-Endpunkt für Load Balancer und Monitoring.
Prüft, ob der Service betriebsbereit ist.
"""
return jsonify({"status": "healthy", "service": "langchain-api"}), 200
@app.route('/api/v1/ask', methods=['GET'])
def ask_question():
"""
Synchroner Endpunkt für LLM-Anfragen.
Query-Parameter:
question (str): Die zu beantwortende Frage
Returns:
JSON-Response mit strukturierter Antwort oder Fehler
"""
# Question aus Query-Parameter extrahieren
question = request.args.get('question')
# Validierung
if not question:
error = ErrorResponse(
error="Parameter 'question' ist erforderlich",
status_code=400
)
return jsonify(error.model_dump()), 400
if len(question) > 1000:
error = ErrorResponse(
error="Frage darf maximal 1000 Zeichen lang sein",
status_code=400
)
return jsonify(error.model_dump()), 400
try:
logger.info(f"Verarbeite Anfrage: {question[:100]}...")
# LLM aufrufen
response = llm.invoke(question)
# Response-Content extrahieren
# Das genaue Format hängt vom Model ab
answer_text = response.content if hasattr(response, 'content') else str(response)
# Token-Usage extrahieren, falls verfügbar
tokens = None
if hasattr(response, 'response_metadata'):
usage = response.response_metadata.get('token_usage', {})
tokens = usage.get('total_tokens')
# Strukturierte Antwort erstellen
llm_response = LLMResponse(
question=question,
answer=answer_text,
model="gpt-4o-mini",
tokens_used=tokens
)
logger.info(f"Anfrage erfolgreich verarbeitet. Tokens: {tokens}")
return jsonify(llm_response.model_dump()), 200
except Exception as e:
logger.error(f"Fehler bei Verarbeitung: {str(e)}", exc_info=True)
error = ErrorResponse(
error=f"Interner Fehler: {str(e)}",
status_code=500
)
return jsonify(error.model_dump()), 500
@app.route('/api/v1/ask', methods=['POST'])
def ask_question_post():
"""
POST-Variante für komplexere Anfragen.
Erlaubt zusätzliche Parameter wie Temperatur, max_tokens, etc.
Request Body (JSON):
{
"question": "Die Frage",
"temperature": 0.7, # optional
"max_tokens": 500 # optional
}
"""
# JSON-Body parsen
if not request.is_json:
error = ErrorResponse(
error="Content-Type muss application/json sein",
status_code=400
)
return jsonify(error.model_dump()), 400
data = request.get_json()
question = data.get('question')
if not question:
error = ErrorResponse(
error="Feld 'question' ist erforderlich",
status_code=400
)
return jsonify(error.model_dump()), 400
try:
# Optional: Parameter aus Request übernehmen
temperature = data.get('temperature', 0.7)
max_tokens = data.get('max_tokens')
# LLM mit custom Parameters
custom_llm = init_chat_model(
"gpt-4o-mini",
temperature=temperature,
max_tokens=max_tokens
)
response = custom_llm.invoke(question)
answer_text = response.content if hasattr(response, 'content') else str(response)
# Token-Usage
tokens = None
if hasattr(response, 'response_metadata'):
usage = response.response_metadata.get('token_usage', {})
tokens = usage.get('total_tokens')
llm_response = LLMResponse(
question=question,
answer=answer_text,
model="gpt-4o-mini",
tokens_used=tokens
)
return jsonify(llm_response.model_dump()), 200
except Exception as e:
logger.error(f"Fehler: {str(e)}", exc_info=True)
error = ErrorResponse(
error=f"Interner Fehler: {str(e)}",
status_code=500
)
return jsonify(error.model_dump()), 500
if __name__ == '__main__':
# Development-Server
# In Produktion: Gunicorn, uWSGI oder ähnliches verwenden
app.run(host='0.0.0.0', port=5000, debug=False)Dieser Service ist funktional, aber grundlegend. Er nimmt Fragen entgegen, ruft das LLM auf, gibt strukturierte Antworten zurück. Die Pydantic-Models garantieren, dass die Response-Struktur konsistent ist. Validierung verhindert ungültige Inputs. Error Handling fängt Exceptions und gibt aussagekräftige Fehler zurück. Logging ermöglicht Debugging und Monitoring.
Was fehlt? Authentifizierung. Rate Limiting. Request-Caching. Metrics-Export für Prometheus. Distributed Tracing. All das sind Produktions-Anforderungen, die man hinzufügen würde. Doch das Grundprinzip ist da: LangChain wird zum HTTP-Service.
Der kritische Punkt: Das LLM wird einmal beim Start initialisiert, nicht pro Request. Model-Initialisierung ist teuer. Würde man es pro Request machen, wäre die Latenz katastrophal. Stattdessen ist das LLM eine langlebige Instanz, die Requests sequenziell verarbeitet. Das ist für synchrone, low-concurrency-Szenarien akzeptabel.
Flask ist synchron. Jeder Request blockiert einen Worker-Thread bis zur Completion. Bei LLM-Aufrufen, die Sekunden dauern können, ist das problematisch. Ein Worker kann nur einen Request gleichzeitig handhaben. Bei zehn parallelen Requests braucht man zehn Worker. Das skaliert schlecht.
FastAPI bietet native Async-Unterstützung. Async I/O erlaubt es einem Worker, während ein LLM-Request auf Antwort wartet, andere Requests zu verarbeiten. Statt zehn Worker für zehn parallele Requests reichen vielleicht zwei oder drei. Die Effizienz steigt dramatisch.
Hinzu kommt: FastAPI generiert automatisch OpenAPI-Dokumentation. Man bekommt Swagger UI kostenlos. Type Hints werden zu API-Schema. Validierung ist eingebaut via Pydantic. Das macht FastAPI zur besseren Wahl für produktive LLM-Services.
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from typing import Optional
import asyncio
import logging
from langchain.chat_models import init_chat_model
# Logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# FastAPI-App
app = FastAPI(
title="LangChain API Service",
description="REST API für LLM-basierte Frage-Antwort-Funktionalität",
version="1.0.0",
docs_url="/docs", # Swagger UI unter /docs
redoc_url="/redoc" # ReDoc unter /redoc
)
# LLM initialisieren
# Für async: Modelle, die async unterstützen, nutzen
llm = init_chat_model("gpt-4o-mini", temperature=0.7)
# Pydantic-Models für Request/Response
class AskRequest(BaseModel):
"""Request-Schema für POST /api/v1/ask"""
question: str = Field(
...,
min_length=1,
max_length=1000,
description="Die zu beantwortende Frage"
)
temperature: Optional[float] = Field(
default=0.7,
ge=0.0,
le=2.0,
description="Temperatur-Parameter für Kreativität (0.0-2.0)"
)
max_tokens: Optional[int] = Field(
default=None,
gt=0,
description="Maximale Anzahl generierter Tokens"
)
class LLMResponse(BaseModel):
"""Response-Schema für erfolgreiche Anfragen"""
question: str = Field(description="Die ursprüngliche Frage")
answer: str = Field(description="Die generierte Antwort")
model: str = Field(description="Das verwendete Modell")
tokens_used: Optional[int] = Field(
default=None,
description="Verwendete Tokens (wenn verfügbar)"
)
class HealthResponse(BaseModel):
"""Health-Check Response"""
status: str
service: str
version: str
@app.get("/health", response_model=HealthResponse, tags=["System"])
async def health_check():
"""
Health-Check-Endpunkt für Monitoring und Load Balancer.
Gibt Status und Service-Informationen zurück.
"""
return HealthResponse(
status="healthy",
service="langchain-api",
version="1.0.0"
)
@app.get(
"/api/v1/ask",
response_model=LLMResponse,
tags=["LLM"],
summary="Frage beantworten (GET)",
description="Beantwortet eine Frage via Query-Parameter. Synchrone Verarbeitung."
)
async def ask_question_get(
question: str = Query(
...,
min_length=1,
max_length=1000,
description="Die zu beantwortende Frage"
)
):
"""
GET-Endpunkt für einfache Fragen.
Beispiel: /api/v1/ask?question=Was+ist+Async+IO
"""
try:
logger.info(f"GET-Anfrage: {question[:100]}...")
# LLM async aufrufen
# ainvoke ist die async-Variante von invoke
response = await llm.ainvoke(question)
# Response extrahieren
answer_text = response.content if hasattr(response, 'content') else str(response)
# Token-Usage
tokens = None
if hasattr(response, 'response_metadata'):
usage = response.response_metadata.get('token_usage', {})
tokens = usage.get('total_tokens')
logger.info(f"Anfrage erfolgreich. Tokens: {tokens}")
return LLMResponse(
question=question,
answer=answer_text,
model="gpt-4o-mini",
tokens_used=tokens
)
except Exception as e:
logger.error(f"Fehler: {str(e)}", exc_info=True)
raise HTTPException(
status_code=500,
detail=f"Fehler bei LLM-Verarbeitung: {str(e)}"
)
@app.post(
"/api/v1/ask",
response_model=LLMResponse,
tags=["LLM"],
summary="Frage beantworten (POST)",
description="Beantwortet eine Frage mit optionalen Parametern. Asynchrone Verarbeitung."
)
async def ask_question_post(request: AskRequest):
"""
POST-Endpunkt für komplexere Anfragen mit Parametern.
Request Body:
```json
{
"question": "Erkläre Async I/O",
"temperature": 0.5,
"max_tokens": 300
}
```
"""
try:
logger.info(f"POST-Anfrage: {request.question[:100]}...")
# LLM mit custom Parameters
custom_llm = init_chat_model(
"gpt-4o-mini",
temperature=request.temperature,
max_tokens=request.max_tokens
)
# Async invoke
response = await custom_llm.ainvoke(request.question)
answer_text = response.content if hasattr(response, 'content') else str(response)
# Token-Usage
tokens = None
if hasattr(response, 'response_metadata'):
usage = response.response_metadata.get('token_usage', {})
tokens = usage.get('total_tokens')
logger.info(f"POST-Anfrage erfolgreich. Tokens: {tokens}")
return LLMResponse(
question=request.question,
answer=answer_text,
model="gpt-4o-mini",
tokens_used=tokens
)
except Exception as e:
logger.error(f"Fehler: {str(e)}", exc_info=True)
raise HTTPException(
status_code=500,
detail=f"Fehler bei LLM-Verarbeitung: {str(e)}"
)
# Streaming-Endpunkt für lange Antworten
@app.post(
"/api/v1/ask/stream",
tags=["LLM"],
summary="Frage mit Streaming beantworten",
description="Streamt die Antwort Token-für-Token. Nützlich für lange Antworten."
)
async def ask_question_stream(request: AskRequest):
"""
Streaming-Endpunkt für progressive Antworten.
Gibt Server-Sent Events (SSE) zurück, die die Antwort stückweise liefern.
"""
from fastapi.responses import StreamingResponse
async def generate():
"""Generator für SSE-Stream."""
try:
custom_llm = init_chat_model(
"gpt-4o-mini",
temperature=request.temperature,
streaming=True # Streaming aktivieren
)
# astream liefert Tokens asynchron
async for chunk in custom_llm.astream(request.question):
content = chunk.content if hasattr(chunk, 'content') else str(chunk)
# SSE-Format: data: <content>\n\n
yield f"data: {content}\n\n"
# Stream-Ende signalisieren
yield "data: [DONE]\n\n"
except Exception as e:
logger.error(f"Stream-Fehler: {str(e)}", exc_info=True)
yield f"data: ERROR: {str(e)}\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream"
)
if __name__ == "__main__":
import uvicorn
# Development-Server mit Hot-Reload
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True,
log_level="info"
)Dieser FastAPI-Service demonstriert mehrere fortgeschrittene
Konzepte. Erstens: Native Async-Unterstützung via async def
und await. Das LLM wird mit ainvoke aufgerufen
– der asynchronen Variante von invoke. Während das LLM
antwortet, kann der Worker andere Requests handhaben.
Zweitens: Automatische API-Dokumentation. Startet man den Service und
navigiert zu http://localhost:8000/docs, bekommt man eine
interaktive Swagger-UI. Jeder Endpunkt ist dokumentiert, inklusive
Request-Schemas, Response-Schemas, Beispiele. Man kann direkt im Browser
Requests testen. Das ist unbezahlbar für Entwickler, die die API
integrieren wollen.
Drittens: Ein Streaming-Endpunkt. Für lange Antworten ist Request-Response problematisch. Der Client wartet zehn, zwanzig Sekunden auf eine vollständige Antwort. Streaming löst das. Die Antwort wird Token-für-Token gestreamt via Server-Sent Events. Der Client kann sofort beginnen, Tokens zu rendern. Die wahrgenommene Latenz sinkt dramatisch. Das ist die Grundlage für ChatGPT-ähnliche UIs, wo die Antwort “live” erscheint.
FastAPI generiert OpenAPI-Spezifikationen automatisch. Diese Spezifikation ist ein maschinenlesbares Schema der API – welche Endpunkte existieren, welche Parameter sie erwarten, welche Responses sie liefern. Das Schema kann exportiert werden und als Basis für Client-Generierung dienen.
# OpenAPI-Schema exportieren
curl http://localhost:8000/openapi.json > langchain-api-spec.json
# Client-Code generieren mit openapi-generator
# Beispiel: TypeScript-Client für Frontend
openapi-generator-cli generate \
-i langchain-api-spec.json \
-g typescript-axios \
-o ./generated-client
# Oder Python-Client für Backend-Integration
openapi-generator-cli generate \
-i langchain-api-spec.json \
-g python \
-o ./python-clientDas Resultat ist ein vollständig typsicherer Client. Frontend-Entwickler importieren den generierten TypeScript-Code und haben sofort Autocomplete, Type-Checking, Validierung. Backend-Entwickler in anderen Services nutzen den Python-Client und integrieren die LLM-Funktionalität ohne manuelles HTTP-Handling.
Diese Automatisierung ist der Kern von API-First-Design. Die API-Spezifikation ist die Source of Truth. Server-Code, Client-Code, Dokumentation – alles wird daraus generiert. Änderungen an der API führen automatisch zu Updates in allen Clients. Das reduziert Integrationsfehler und beschleunigt Entwicklung.
Ein lokaler Development-Server ist schön, aber Produktion hat andere Anforderungen. Skalierung, Hochverfügbarkeit, Monitoring, Security. Wie bringt man einen LangChain-Service in die Produktion?
Die erste Wahl: Container. Dockerisierung kapselt Abhängigkeiten. Ein Dockerfile für den FastAPI-Service könnte so aussehen:
FROM python:3.11-slim
# Working Directory
WORKDIR /app
# Dependencies installieren
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# App-Code kopieren
COPY . .
# Port exponieren
EXPOSE 8000
# Health-Check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Production-Server (Uvicorn mit mehreren Workern)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]Dieser Container ist produktionsreif. Multiple Workers handhaben parallele Requests. Ein Health-Check erlaubt Orchestratoren wie Kubernetes zu verifizieren, dass der Service läuft. Die Konfiguration ist über Environment Variables steuerbar.
Der nächste Schritt: Orchestrierung. Kubernetes ist der Standard für Cloud-Native-Deployment. Ein simples Kubernetes-Manifest könnte den Service deployen, skalieren und exponieren:
apiVersion: apps/v1
kind: Deployment
metadata:
name: langchain-api
spec:
replicas: 3 # Drei Instanzen für Hochverfügbarkeit
selector:
matchLabels:
app: langchain-api
template:
metadata:
labels:
app: langchain-api
spec:
containers:
- name: langchain-api
image: your-registry/langchain-api:latest
ports:
- containerPort: 8000
env:
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: llm-secrets
key: openai-api-key
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "2000m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: langchain-api-service
spec:
selector:
app: langchain-api
ports:
- protocol: TCP
port: 80
targetPort: 8000
type: LoadBalancerDieses Manifest deployt drei Replicas des Services hinter einem Load Balancer. Liveness und Readiness Probes stellen sicher, dass nur gesunde Instanzen Traffic bekommen. Resource Limits verhindern, dass eine Instanz den gesamten Cluster monopolisiert. Secrets werden sicher über Kubernetes Secrets injiziert, nicht hardcoded.
Die Skalierung ist nun trivial. Mehr Traffic? Erhöhe die Replica-Zahl. Kubernetes verteilt automatisch. Ein Horizontal Pod Autoscaler könnte sogar automatisch skalieren basierend auf CPU oder Custom Metrics wie Request-Rate.
Ein Service ohne Monitoring ist ein Blackbox. Wenn etwas schief geht, weiß man nicht warum. Observability – die Fähigkeit, den internen Zustand aus externen Outputs abzuleiten – ist entscheidend.
Drei Säulen der Observability: Logs, Metrics, Traces. Logs haben wir bereits – strukturierte Logs via Python Logging. Metrics sind quantitative Messungen – Request-Rate, Latenz, Error-Rate. Traces zeigen den Pfad einzelner Requests durch das System.
Für Metrics: Prometheus-Integration via
prometheus-fastapi-instrumentator.
from prometheus_fastapi_instrumentator import Instrumentator
app = FastAPI(...)
# Prometheus-Metrics automatisch exponieren
Instrumentator().instrument(app).expose(app)
# Jetzt unter /metrics verfügbar
# Prometheus kann diese scrapenDas exponiert Standard-Metrics wie http_requests_total,
http_request_duration_seconds, und mehr. Custom Metrics
können hinzugefügt werden – etwa llm_tokens_used_total oder
llm_request_duration_seconds.
Für Traces: OpenTelemetry-Integration. Distributed Tracing zeigt, wie ein Request durch Microservices fließt. Wenn der LangChain-Service andere Services aufruft (Vector Store, externe APIs), wird der gesamte Pfad sichtbar.
from opentelemetry import trace
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
# Tracer konfigurieren
trace.set_tracer_provider(TracerProvider())
tracer = trace.get_tracer(__name__)
# OTLP-Exporter (sendet zu Jaeger, Tempo, etc.)
otlp_exporter = OTLPSpanExporter(endpoint="http://jaeger:4317")
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(otlp_exporter)
)
# FastAPI automatisch instrumentieren
FastAPIInstrumentor.instrument_app(app)Jetzt wird jeder Request als Span getraced. Wenn der Service andere Services aufruft, werden Child-Spans erzeugt. In einer Tracing-UI wie Jaeger sieht man die gesamte Request-Kette – von der initialen HTTP-Anfrage über den LLM-Aufruf bis zur Response. Bottlenecks werden sofort sichtbar.
Wir haben die Perspektive komplett gedreht. Zu Beginn dieses Buchs war LangChain der Konsument – es rief APIs auf, fragte Datenbanken ab, durchsuchte Vector Stores. Jetzt ist es der Provider. Andere Services rufen LangChain auf. Die LLM-Logik ist gekapselt hinter HTTP-Endpunkten.
Diese Umkehrung ist mehr als technisches Detail. Sie ist architektonische Philosophie. In einer Microservice-Landschaft hat jeder Service eine klar definierte Verantwortung. Der LangChain-Service ist verantwortlich für LLM-Interaktion. Andere Services müssen sich damit nicht auseinandersetzen. Sie konsumieren einfach die API.
Das erlaubt Spezialisierung. Das LLM-Team kann sich auf Prompt-Engineering, Model-Auswahl, Retrieval-Optimierung konzentrieren. Frontend-Teams konsumieren die API ohne LLM-Expertise. Backend-Teams integrieren KI-Funktionalität ohne LangChain zu verstehen. Jedes Team arbeitet in seiner Domäne.
Das erlaubt auch Technologie-Freiheit. Der LangChain-Service ist in Python. Doch Clients können in jeder Sprache sein – TypeScript, Java, Go, Rust. HTTP ist die universelle Sprache. OpenAPI ist das universelle Vertragsformat. Polyglotte Architekturen werden möglich.
Und es erlaubt Evolution. Morgen könnte man von GPT-4 zu Claude
wechseln. Übermorgen könnte man RAG mit Vector Stores hinzufügen.
Nächste Woche könnte man Multi-Step-Agents implementieren. Solange das
API-Interface stabil bleibt, merken Clients nichts davon.
Rückwärtskompatibilität wird zum Designprinzip. Breaking Changes werden
über API-Versionierung gehandhabt – /api/v1/ask vs
/api/v2/ask.
Das ist moderne Software-Architektur. LangChain ist nicht mehr ein Skript auf einem Laptop. Es ist ein Service in einem Cluster. Es skaliert horizontal. Es hat Health-Checks und Metrics. Es ist observierbar und debuggbar. Es folgt den gleichen Prinzipien wie jeder andere Microservice – Single Responsibility, API-First-Design, Cloud-Native-Deployment.
LangChain als Service ist LangChain erwachsen geworden.
Von experimentellem Code zu produktiver Infrastruktur. Von lokalen Notebooks zu verteilten Systemen. Von Prototypen zu APIs, die von Dutzenden anderen Services konsumiert werden. Das ist die Reise, die jede erfolgreiche KI-Anwendung durchläuft. Sie beginnt mit einem Prompt. Sie endet mit einem Kubernetes-Cluster.