In a company of 500 employees, knowledge is stored across 7–12 systems simultaneously: Confluence, SharePoint, Jira, corporate email, 1C, internal CRMs, file servers. An employee spends an average of 2.5 hours per day searching for information — this is not a metaphor, it's data from McKinsey Global Institute. The key problem is not that data exists, but that finding it through ordinary keyword search is impossible: a document is named 'Regulation_v3_final_FINAL2', and the query is 'how to book a business trip'. We develop AI document search — a system that solves this problem through semantic understanding of the query and hybrid search across all sources simultaneously.
Technology Approach
Problems We Solve
Data silos are not the only complexity. Even if a document is found, it may be outdated or irrelevant to the context. Typical scenarios:
- Low keyword search precision: the part number 'A-123' is searched as an ordinary word, without understanding it is a part number.
- Lack of context: the query 'marketing budget' should consider that the user is a department head, not an intern.
- Security breaches: an employee sees documents they should not have access to if ACL is not implemented.
Why AI Enterprise Search Is More Effective Than Regular Search
Pure vector search works well on semantically similar queries but poorly on exact matches: part numbers, names, dates. The query 'contract with Romashka LLC dated March 12' yields better results via BM25, while 'procedure for data breach incident' works better via vector search. The hybrid approach combines the strengths of both methods.
| Search Type | Strengths | Weaknesses |
|---|---|---|
| Keyword (BM25) | Exact matches, part numbers, dates | Does not understand synonyms, context |
| Vector (Embeddings) | Semantics, synonyms, generalizations | Poor on rare terms, cold start |
| Hybrid (ours) | Best of both worlds, CrossEncoder reranking | More complex implementation, requires fine-tuning |
from qdrant_client import QdrantClient
from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer, CrossEncoder
import numpy as np
class HybridSearchEngine:
def __init__(self):
self.dense_model = SentenceTransformer("intfloat/multilingual-e5-large")
self.reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
self.qdrant = QdrantClient(url="http://localhost:6333")
self.bm25_index = None
self.doc_store = {}
def search(self, query: str, top_k: int = 20, final_k: int = 5) -> list[dict]:
# Dense search
query_emb = self.dense_model.encode(
f"query: {query}", # E5 format
normalize_embeddings=True
)
dense_results = self.qdrant.search(
collection_name="enterprise_docs",
query_vector=query_emb.tolist(),
limit=top_k
)
# Sparse search (BM25)
bm25_scores = self.bm25_index.get_scores(query.lower().split())
top_bm25_ids = np.argsort(bm25_scores)[-top_k:][::-1]
# Merge results (RRF - Reciprocal Rank Fusion)
merged = self._reciprocal_rank_fusion(dense_results, top_bm25_ids)
# Cross-encoder reranking
pairs = [(query, self.doc_store[doc_id]["text"]) for doc_id in merged[:top_k]]
rerank_scores = self.reranker.predict(pairs)
reranked = sorted(
zip(merged[:top_k], rerank_scores),
key=lambda x: x[1],
reverse=True
)
return [self.doc_store[doc_id] for doc_id, _ in reranked[:final_k]]
def _reciprocal_rank_fusion(self, dense: list, sparse: list, k: int = 60) -> list:
scores = {}
for rank, item in enumerate(dense):
doc_id = item.id
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
for rank, doc_id in enumerate(sparse):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
return sorted(scores, key=scores.get, reverse=True)
Query Understanding and Answer Generation
Our RAG system combines the hybrid search engine with a language model to generate precise answers from company documents.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
class EnterpriseSearchAssistant:
def __init__(self, search_engine: HybridSearchEngine):
self.search = search_engine
self.llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.1)
def answer(self, query: str, user_context: dict) -> dict:
# Expand query for better search
expanded_query = self._expand_query(query, user_context)
# Search across all sources
results = self.search.search(expanded_query, final_k=7)
if not results:
return {"answer": "No documents found for your query.", "sources": []}
# Generate answer with citations
context = "\n\n".join([
f"[{i+1}] {r['title']} ({r['source']})\n{r['text'][:500]}"
for i, r in enumerate(results)
])
prompt = f"""Answer the employee's question based on company documents.
Use only the provided documents. Cite sources [1], [2], etc.
If the answer is incomplete, say so.
Question: {query}
Documents:
{context}"""
answer = self.llm.invoke(prompt).content
return {
"answer": answer,
"sources": [{"id": i+1, "title": r["title"], "url": r["url"]}
for i, r in enumerate(results)]
}
Implementation and Security
Source Indexing
Each source has a separate connector with incremental updates:
| Source | Connector | Indexing Frequency | Peculiarities |
|---|---|---|---|
| Confluence | REST API (spaces, pages) | Every 30 min | Confluence markup processing |
| SharePoint | Microsoft Graph API | Every hour | Word/PDF/PPTX support |
| Jira | REST API | Every 15 min | Tickets + comments |
| IMAP/Exchange | Realtime (webhooks) | Only sent/received | |
| File server | Inotify / polling | On change | PDF, DOCX, XLSX, TXT |
How We Ensure Corporate Data Security
A critical detail of access-rights search — users must see only documents they are authorized to access. Implemented via metadata filters in Qdrant:
# When searching, filter by user permissions
results = qdrant.search(
collection_name="enterprise_docs",
query_vector=query_emb,
query_filter=Filter(
must=[
FieldCondition(
key="allowed_groups",
match=MatchAny(any=user_groups)
)
]
),
limit=20
)
Example of access control configuration
ACL is configured via document metadata: groups, roles, permissions. The Qdrant vector database supports filtering at the point (document) level, ensuring security.Our Process
- Analytics and source audit: identify where data resides, estimate volume and change frequency.
- Architecture design: choose embedding model, vector DB, configure pipeline.
- Data indexing: connect connectors, perform trial indexing on 10% of data.
- Search and reranking tuning: optimize RRF parameters, set thresholds for CrossEncoder.
- UI and bot integration: embed search in web interface, Slack bot, API.
- Testing and debugging: test on real employee queries, iteratively improve.
- Deployment and monitoring: deploy to production, set up logs and metrics (latency p99, recall).
What Is Included
- Architecture documentation and operational instructions.
- Configured connectors to all sources.
- Web search interface and integration with corporate messenger.
- Team training (2-3 workshops).
- 3-month warranty support after launch.
- Access control setup documentation.
Timelines and Cost
- Pilot (1-2 sources, ~50,000 documents): 4–6 weeks.
- Full system (5–8 sources + UI): 3–4 months.
- ACL setup: +2–3 weeks.
Cost is calculated individually after audit. On average, investments pay back in 6–12 months due to reduced search time. For a 500-employee company, we estimate annual savings of $200,000 based on reduced search time. Order a pilot project — the first stage takes no more than 2 days for analysis.
Results and Case Study
One of our clients, a manufacturing holding company with 1200 employees. We indexed 340,000 documents from Confluence, SharePoint, and a custom document management system. Index loading time was 18 hours (one-time). Regular updates — delta of 30 minutes, ~200 documents, taking 4 minutes. Average top-3 accuracy (evaluated by HR team on a sample of 500 queries): 78% relevant results compared to 41% with the previous Elasticsearch keyword search (1.9 times better). By automating search, the company achieved significant budget savings — approximately $150,000 annually.
How Search Accuracy Is Evaluated
Evaluation is performed on real employee queries. We form a sample of 300–500 typical questions, label relevant documents, and compute recall@k and precision@k. The key tool is CrossEncoder, which ranks candidates and improves top-3 accuracy to 78%. Validation is done by the client on their data.
Why Choose Us
- 5+ years in enterprise AI solutions.
- 20+ projects implementing semantic search in companies from 200 to 5000 employees.
- Guarantee of SLA 99.9% and search accuracy no less than 75% according to your criteria.
- Certified MLOps and NLP engineers.
Contact us to get a consultation on your project and a preliminary estimate within 2 days.







