KOBAI: AI-powered document management at Schott Pharma

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

  1. Local LLMs: Sufficient quality for business use
  2. Hybrid approachOptimal combination of vector search and LLM
  3. Chunking strategiesKey to answer quality
  4. CachingCritical for performance with repeated requests

Organizational insights

  1. Change ManagementIntensive training required
  2. Stakeholder engagementEarly involvement of all departments
  3. Iterative developmentAgile methods for AI projects too
  4. 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!

Sounds like your project?

Write to me or start a non-binding project request.

Project requestContact