KOBAI: AI-powered document management at Schott Pharma
Gewinner des Digital Game-Changer Award 2024
The digitalization of the pharmaceutical industry presents unique challenges for data privacy and compliance. With KOBAI, we’ve developed an innovative AI solution that tackles these challenges while dramatically boosting efficiency.
Project challenge
Schott Pharma, a leading manufacturer of pharmaceutical packaging, faced the following challenges:
Initial situation
- document volume>50,000 technical documents
- search timeAverage of 45 minutes per request
- ComplianceStrict DSGVO and FDA requirements
- ConfidentialityNo cloud solutions allowed
- Multilingualism: German, English, other EU languages
Business impact
- Delays in product development
- High personnel costs for document search
- Risk of compliance violations
- Inefficient knowledge distribution
Solution approach: KOBAI
KOBAI (Knowledge Organization with Business AI) is a fully local AI solution based on offline large language models.
Core components
Lokale LLM-Integration
# LM Studio Integration
from openai import OpenAI
class LocalLLMClient:
def __init__(self, base_url="http://localhost:1234/v1"):
self.client = OpenAI(
base_url=base_url,
api_key="lm-studio" # Dummy-Key für lokale Instanz
)
def query_documents(self, question, context):
response = self.client.chat.completions.create(
model="microsoft/DialoGPT-medium", # Lokales Modell
messages=[
{"role": "system", "content": "Du bist ein Experte für Pharmadokumentation."},
{"role": "user", "content": f"Kontext: {context}nnFrage: {question}"}
],
temperature=0.1, # Niedrige Temperatur für präzise Antworten
max_tokens=500
)
return response.choices[0].message.content
Dokumentenindizierung
// C# Backend für Dokumentenverarbeitung
public class DocumentProcessor
{
private readonly IVectorDatabase _vectorDb;
private readonly ITextExtractor _textExtractor;
public async Task<ProcessingResult> ProcessDocument(DocumentInfo doc)
{
try
{
// Text extrahieren
var extractedText = await _textExtractor.ExtractAsync(doc.FilePath);
// Chunking für bessere Verarbeitung
var chunks = SplitIntoChunks(extractedText, maxChunkSize: 1000);
// Embeddings generieren (lokal)
var embeddings = await GenerateEmbeddings(chunks);
// In Vektordatenbank speichern
await _vectorDb.StoreAsync(doc.Id, embeddings, chunks);
return new ProcessingResult { Success = true, ChunksProcessed = chunks.Count };
}
catch (Exception ex)
{
_logger.LogError(ex, "Fehler bei Dokumentenverarbeitung: {DocumentId}", doc.Id);
return new ProcessingResult { Success = false, Error = ex.Message };
}
}
private List<string> SplitIntoChunks(string text, int maxChunkSize)
{
var chunks = new List<string>();
var sentences = text.Split('.', StringSplitOptions.RemoveEmptyEntries);
var currentChunk = new StringBuilder();
foreach (var sentence in sentences)
{
if (currentChunk.Length + sentence.Length > maxChunkSize)
{
if (currentChunk.Length > 0)
{
chunks.Add(currentChunk.ToString().Trim());
currentChunk.Clear();
}
}
currentChunk.Append(sentence + ". ");
}
if (currentChunk.Length > 0)
{
chunks.Add(currentChunk.ToString().Trim());
}
return chunks;
}
}
Semantische Suchmaschine
public class SemanticSearchEngine
{
private readonly IVectorDatabase _vectorDb;
private readonly LocalLLMClient _llmClient;
public async Task<SearchResult> SearchAsync(string query, SearchOptions options)
{
// Query-Embedding generieren
var queryEmbedding = await GenerateQueryEmbedding(query);
// Ähnliche Dokumente finden
var similarChunks = await _vectorDb.FindSimilarAsync(
queryEmbedding,
topK: options.MaxResults,
threshold: options.SimilarityThreshold
);
// Kontext für LLM aufbauen
var context = BuildContext(similarChunks);
// LLM-Antwort generieren
var llmResponse = await _llmClient.QueryDocuments(query, context);
return new SearchResult
{
Answer = llmResponse,
SourceDocuments = similarChunks.Select(c => c.DocumentInfo).ToList(),
Confidence = CalculateConfidence(similarChunks),
ProcessingTime = stopwatch.ElapsedMilliseconds
};
}
}
Technical architecture
Systemübersicht
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Web Frontend │────│ C# Backend │────│ LM Studio │
│ - React │ │ - .NET 8 │ │ - Offline LLM │
│ - TypeScript │ │ - Entity FW │ │ - Local API │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│ │ │
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ User Auth │ │ Vector Database │ │ Document Store │
│ - Active Dir │ │ - Chroma DB │ │ - File System │
│ - LDAP │ │ - Embeddings │ │ - Metadata DB │
└─────────────────┘ └──────────────────┘ └─────────────────┘
Security architecture
Privacy features
public class SecurityManager
{
public async Task<bool> ValidateAccess(User user, Document document)
{
// Rollenbasierte Zugriffskontrolle
var userRoles = await GetUserRoles(user.Id);
var requiredRoles = document.AccessRequirements;
if (!userRoles.Any(r => requiredRoles.Contains(r)))
{
await LogAccessDenied(user.Id, document.Id, "Insufficient roles");
return false;
}
// Abteilungsbasierte Filterung
if (document.Department != null && user.Department != document.Department)
{
await LogAccessDenied(user.Id, document.Id, "Department mismatch");
return false;
}
// Audit-Log
await LogDocumentAccess(user.Id, document.Id, DateTime.UtcNow);
return true;
}
public string EncryptSensitiveData(string data)
{
using var aes = Aes.Create();
aes.Key = GetEncryptionKey();
aes.IV = GenerateIV();
using var encryptor = aes.CreateEncryptor();
var dataBytes = Encoding.UTF8.GetBytes(data);
var encryptedBytes = encryptor.TransformFinalBlock(dataBytes, 0, dataBytes.Length);
return Convert.ToBase64String(encryptedBytes);
}
}
Implementation Details
Front-End-Entwicklung
// React-Komponente für intelligente Suche
interface SearchComponentProps {
onResultsFound: (results: SearchResult[]) => void;
}
const IntelligentSearch: React.FC<SearchComponentProps> = ({ onResultsFound }) => {
const [query, setQuery] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [suggestions, setSuggestions] = useState<string[]>([]);
const handleSearch = async () => {
setIsLoading(true);
try {
const response = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
options: {
maxResults: 10,
includeMetadata: true,
similarityThreshold: 0.7
}
})
});
const results = await response.json();
onResultsFound(results);
} catch (error) {
console.error('Search failed:', error);
} finally {
setIsLoading(false);
}
};
// Auto-Suggest Implementation
useEffect(() => {
const debounced = debounce(async (searchTerm: string) => {
if (searchTerm.length > 2) {
const suggestions = await fetchSuggestions(searchTerm);
setSuggestions(suggestions);
}
}, 300);
debounced(query);
}, [query]);
return (
<div className="search-container">
<SearchInput
value={query}
onChange={setQuery}
onSearch={handleSearch}
suggestions={suggestions}
isLoading={isLoading}
/>
<SearchFilters />
<SearchHistory />
</div>
);
};
Performance Optimization
public class CacheManager
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan expiration)
{
// Erst Memory Cache prüfen
if (_memoryCache.TryGetValue(key, out T cachedValue))
{
return cachedValue;
}
// Dann Distributed Cache
var distributedValue = await _distributedCache.GetStringAsync(key);
if (distributedValue != null)
{
var deserializedValue = JsonSerializer.Deserialize<T>(distributedValue);
_memoryCache.Set(key, deserializedValue, TimeSpan.FromMinutes(5));
return deserializedValue;
}
// Wert generieren und cachen
var newValue = await factory();
var serializedValue = JsonSerializer.Serialize(newValue);
await _distributedCache.SetStringAsync(key, serializedValue, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = expiration
});
_memoryCache.Set(key, newValue, TimeSpan.FromMinutes(5));
return newValue;
}
}
Provisioning and operation
Containerization
# Dockerfile für KOBAI Backend
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["KOBAI.API/KOBAI.API.csproj", "KOBAI.API/"]
COPY ["KOBAI.Core/KOBAI.Core.csproj", "KOBAI.Core/"]
RUN dotnet restore "KOBAI.API/KOBAI.API.csproj"
COPY . .
WORKDIR "/src/KOBAI.API"
RUN dotnet build "KOBAI.API.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "KOBAI.API.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
# LM Studio Installation
RUN apt-get update && apt-get install -y
curl
python3
python3-pip
# Lokale LLM-Modelle
COPY models/ /app/models/
ENTRYPOINT ["dotnet", "KOBAI.API.dll"]
Monitoring and logging
public class ApplicationInsights
{
private readonly ILogger<ApplicationInsights> _logger;
private readonly TelemetryClient _telemetryClient;
public void TrackSearchQuery(string query, int resultsCount, long processingTime)
{
_telemetryClient.TrackEvent("SearchQuery", new Dictionary<string, string>
{
["Query"] = HashQuery(query), // Anonymisiert
["ResultsCount"] = resultsCount.ToString(),
["ProcessingTime"] = processingTime.ToString()
});
_logger.LogInformation("Search completed: {ResultsCount} results in {ProcessingTime}ms",
resultsCount, processingTime);
}
public void TrackDocumentAccess(string userId, string documentId)
{
_telemetryClient.TrackEvent("DocumentAccess", new Dictionary<string, string>
{
["UserId"] = HashUserId(userId),
["DocumentType"] = GetDocumentType(documentId)
});
}
}
Results and impact
Quantitative improvements
- search timeFrom 45 minutes to 30 seconds (-98.9%)
- Hit accuracy: 94% relevant results
- User acceptance7% positive ratings
- time savings0 hours per week per department
- return on investment40% increase in the first year
Qualitative improvements
- knowledge transferBetter dissemination of expert knowledge
- Compliance: 100% GDPR-compliant
- employee satisfactionLess frustration when searching for documents
- InnovationFaster product development through enhanced information access
Technical KPIs
public class PerformanceMetrics
{
public class SearchMetrics
{
public double AverageResponseTime { get; set; } = 847; // ms
public double P95ResponseTime { get; set; } = 1200; // ms
public double AccuracyScore { get; set; } = 0.94;
public int QueriesPerDay { get; set; } = 1247;
}
public class SystemMetrics
{
public double CpuUtilization { get; set; } = 0.23;
public double MemoryUtilization { get; set; } = 0.67;
public double DiskUtilization { get; set; } = 0.45;
public double Uptime { get; set; } = 0.9998; // 99.98%
}
}
Award: Digital Game-Changer Award
KOBAI was awarded the Digital Game-Changer Award in 2024 for:
- InnovationFirst fully local AI solution in the pharmaceutical industry
- ImpactDramatic efficiency increase with the highest security standards
- scalabilityTransferability to other regulated industries
- SustainabilityReducing CO2 footprint through local processing
Lessons Learned
Technical Insights
- Local LLMs: Sufficient quality for business use
- Hybrid approachOptimal combination of vector search and LLM
- Chunking strategiesKey to answer quality
- CachingCritical for performance with repeated requests
Organizational insights
- Change ManagementIntensive training required
- Stakeholder engagementEarly involvement of all departments
- Iterative developmentAgile methods for AI projects too
- ComplianceEarly involvement of the legal department
Future Development
Planned features
- Multimodal searchIntegration of images and diagrams
- Automatic summariesAI-generated document summaries
- Predictive searchPrediction of information needs
- IntegrationConnection to other corporate systems
scaling
public class ScalingStrategy
{
public async Task<DeploymentPlan> PlanScaling(ScalingRequirements requirements)
{
return new DeploymentPlan
{
// Horizontale Skalierung
AdditionalNodes = CalculateRequiredNodes(requirements.ExpectedLoad),
// Vertikale Skalierung
ResourceUpgrade = new ResourceUpgrade
{
CpuCores = requirements.CpuRequirement,
MemoryGB = requirements.MemoryRequirement,
StorageTB = requirements.StorageRequirement
},
// Geografische Verteilung
RegionalDeployments = requirements.Regions.Select(r => new RegionalDeployment
{
Region = r,
LocalLLMModel = SelectOptimalModel(r.Language),
ComplianceRequirements = GetRegionalCompliance(r)
}).ToList()
};
}
}
Conclusion
KOBAI impressively demonstrates how modern AI technologies can be successfully deployed even in highly regulated environments. The key lies in combining technical innovation with strict adherence to data protection and compliance requirements.
The recognition by the Digital Game-Changer Award underscores the innovative nature and measurable business value of the solution. KOBAI is not just a technical success but an example of how AI can positively transform the workplace.
Interested in a similar AI solution for your company? We’d be happy to advise you on developing a tailored, GDPR-compliant AI strategy!
