RAG: от первых принципов к трём основным агентам
Почему веса терпят неудачу с приватными данными, как работают эмбеддинги и метод косинусного поиска, а также три уровня развития агентов от управляемых баз знаний до самоуправляемого сбора данных.
Замороженные модели фундаментального уровня не могут отвечать на вопросы частных компаний, основываясь исключительно на весах. В процессе обучения они никогда не сталкивались с внутренними правилами HR, контрактами или руководствами; момент заморозки останавливает время, и даже при отсутствии фактов генератор продолжает создавать правдоподобные предположения. Поэтому обучение сети тому, что знает компания, превращается в проблему системного характера, а не просто проблему увеличения количества точек контроля.
Почему одних только весов недостаточно
Три структурных ограничения мешают использованию таких моделей для вопросов и ответов в корпорациях:
- Частные наборы данных никогда не использовались в процессе обучения. Правила хранения данных и шаблоны запросов не являются публичными текстами из Интернета.
- Время останавливается на момент заморозки. Изменения в законодательстве, произошедшие в прошлом месяце, недоступны для замороженной копии модели.
- Молчание не является стандартным состоянием. Система предпочитает создавать правдоподобное продолжение текста, а не честно указывать на отсутствие информации.
Три способа внедрения знаний — и какой из них эффективен
Вариант 1: Тонкая настройка на корпусе данных
Дальнейшая обучающая процедура может привить определенный стиль или навыки, однако она размывает факты между параметрами без указания источников и не позволяет контролировать видимость информации для отдельных пользователей, поскольку все используют одинаковые веса. Для систем ответов на вопросы, требующих цитирования и контроля доступа, тонкая настройка является неподходящим механизмом.
Вариант 2: Вставка всего в инструкции системы
Предоставление текста при запросе превращает процесс поиска информации в её чтение, что и является правильным механизмом. Этот подход работает для небольших стабильных наборов данных. Однако он теряет эффективность, когда объем данных превышает допустимые пределы, когда за каждый токен взимается плата при каждом запросе, когда ответы, сформированные внутри промпта, ухудшаются качеством, и когда одна статическая вставка не позволяет фильтровать информацию по ролям пользователей.
Вариант 3: Вставка только важных данных
Сохранить механизм чтения при запросе и скорректировать его масштаб. Получить несколько релевантных абзацев, затем сгенерировать ответ. Это и есть генерация с усилением за счет поиска, сформулированное в одном предложении.
Что такое RAG с нуля до конца
В офлайн-режиме: загружаются файлы, разделяются на сегменты, эти сегменты встраиваются, хранятся векторы (и часто лексические индексы). В онлайн-режиме: встраивается вопрос, запрашиваются наиболее релевантные сегменты, они объединяются в инструкцию с вопросом, после чего генерируется ответ на основе этих сегментов.
Самая сложная часть — это не последний этап генерации ответа, а обеспечение точного, недорогого и учитывающего правила доступа получения необходимых данных.
Векторы и эмбеддинги без мистики
Вектор здесь — это список чисел, который помещает фрагмент текста в пространство, где элементы с похожим значением находятся рядом друг с другом. Сеть эмбеддингов отображает текст в это пространство. Косинусное сходство сравнивает направление больше, чем длину, поэтому оно используется в основном для оценки релевантности. Метрики расстояния, игнорирующие нормализацию, демонстрируют худшую производительность при неравных длинах сегментов.
Выбор размерности и алгоритма вкладки представляет собой коммерческое решение: большее количество измерений позволяет лучше разделять значения, но увеличивает затраты на хранение и задержки; использование разных алгоритмов вкладки во время построения индекса и выполнения запроса косвенно нарушает работу поиска по ближайшему соседу.
Арифметика сходства
Косинус — это скалярное произведение единичных векторов. Два фрагмента о сбросе паролей с разным формулированием всё равно оказываются близко друг к другу, если алгоритм вкладки правильно уловил их смысл. Поиск по ключевым словам упускает эту связь; метод плотного поиска её обнаруживает, но может пропустить точные идентификаторы товаров, если не будет использоваться гибридный лексический поиск.
Создание трех полноценных агентов на основе управляемых облачных сервисов инференса
Остальная часть этого руководства раскрывает эти идеи через три постепенно усовершенствующихся агента на управляемых моделях в стиле AWS Bedrock: тщательно отобранная база знаний, оркестратор LangGraph и самоуправляемый канал ввода данных. Каждое описание представляет собой полную схему агента — рассматривайте их как готовую архитектуру, а не просто фрагменты кода.
Агент 1 — путь к управляемой базе знаний
"""
AGENT 1: Managed Knowledge Base. The 30-line version.
Bedrock does the chunking, embedding, indexing and searching. You wire it up.
"""
import boto3
from langchain_aws import AmazonKnowledgeBasesRetriever, ChatBedrockConverse
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
REGION = "eu-west-1"
KB_ID = "ABCD1234EF"
retriever = AmazonKnowledgeBasesRetriever(
knowledge_base_id=KB_ID,
region_name=REGION,
retrieval_config={
"vectorSearchConfiguration": {
"numberOfResults": 5,
"overrideSearchType": "HYBRID",
}
},
)
llm = ChatBedrockConverse(
model="eu.anthropic.claude-sonnet-4-5-20250929-v1:0",
region_name=REGION,
temperature=0,
max_tokens=1024,
)
PROMPT = ChatPromptTemplate.from_messages([
("system",
"Answer only from the numbered context. Cite every claim as [n]. "
"If the context does not contain the answer, say so plainly."),
("human", "Context:\n{context}\n\nQuestion: {question}"),
])
def format_docs(docs) -> str:
return "\n\n".join(
f"[{i}] source={d.metadata.get('location', {}).get('s3Location', {}).get('uri', '?')}\n"
f"{d.page_content}"
for i, d in enumerate(docs, start=1)
)
chain = (
RunnableParallel(context=retriever | format_docs, question=RunnablePassthrough())
| PROMPT
| llm
| StrOutputParser()
)
if __name__ == "__main__":
print(chain.invoke("How fast must we submit a shortlist for a niche engineering role?"))
Этот подход основан на хостинговой базе знаний: документы сохраняются в хранилище, управляемый индексатор генерирует эмбеддинги, а агент отвечает с цитатами, возвращаемыми этим сервисом. Преимущества включают сокращение необходимости в ручном коде индексации; недостатки — ограниченный контроль над границами сегментов и процессом объединения данных.
Агент 2 — оркестрация с помощью LangGraph
"""
AGENT 2: LangGraph agent that decides WHETHER and WHICH index to search.
Retrieval is a tool. Identity travels in typed context, never in the prompt.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import boto3
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware, ToolCallLimitMiddleware
from langchain.tools import ToolRuntime
from langchain_aws import ChatBedrockConverse
from langchain_core.tools import tool
REGION = "eu-west-1"
POLICY_KB = "ABCD1234EF"
ENGINEERING_KB = "WXYZ5678GH"
agent_runtime = boto3.client("bedrock-agent-runtime", region_name=REGION)
@dataclass
class RequestContext:
"""Built from validated JWT claims. The model cannot read or forge it."""
subject: str
roles: tuple[str, ...]
region: str
def _retrieve(kb_id: str, query: str, ctx: RequestContext, k: int = 5) -> str:
"""Bedrock Retrieve API with a server-side metadata filter for tenant + residency."""
resp = agent_runtime.retrieve(
knowledgeBaseId=kb_id,
retrievalQuery={"text": query},
retrievalConfiguration={
"vectorSearchConfiguration": {
"numberOfResults": k,
"overrideSearchType": "HYBRID",
"filter": {
"andAll": [
{"in": {"key": "acl_roles", "value": list(ctx.roles)}},
{"in": {"key": "region", "value": [ctx.region, "GLOBAL"]}},
]
},
}
},
)
results: list[dict[str, Any]] = resp.get("retrievalResults", [])
if not results:
return "NO_AUTHORISED_RESULTS: nothing in this index that you may read."
return "\n\n".join(
f"[{i}] score={r.get('score'):.3f} "
f"source={r.get('location', {}).get('s3Location', {}).get('uri', '?')}\n"
f"{r['content']['text']}"
for i, r in enumerate(results, start=1)
)
@tool
def search_policy_docs(query: str, runtime: ToolRuntime[RequestContext]) -> str:
"""Search HR, compliance and commercial policy.
Use for shortlist SLAs and service credits, candidate data retention and consent,
GDPR erasure, statement-of-work approval thresholds, background check rules and
automated decision governance.
"""
return _retrieve(POLICY_KB, query, runtime.context)
@tool
def search_engineering_docs(query: str, runtime: ToolRuntime[RequestContext]) -> str:
"""Search platform runbooks and integration documentation.
Use for the requisition sync service, Workday and SuccessFactors webhooks, Kafka
topics and event schemas, idempotency and retry behaviour, and vector index refresh.
"""
return _retrieve(ENGINEERING_KB, query, runtime.context)
agent = create_agent(
model=ChatBedrockConverse(
model="eu.anthropic.claude-sonnet-4-5-20250929-v1:0",
region_name=REGION,
temperature=0,
),
tools=[search_policy_docs, search_engineering_docs],
system_prompt=(
"You are an enterprise knowledge assistant.\n"
"- Answer only from tool results and cite the [n] markers they return.\n"
"- If a tool returns NO_AUTHORISED_RESULTS, say you cannot access a source and "
"name the document owner. Never fill the gap from memory.\n"
"- For questions spanning policy and platform, call both tools before answering."
),
context_schema=RequestContext,
middleware=[
ModelCallLimitMiddleware(thread_limit=8, run_limit=6, exit_behavior="end"),
ToolCallLimitMiddleware(thread_limit=6, run_limit=5, exit_behavior="end"),
],
)
if __name__ == "__main__":
claims = {"sub": "asha@example.com", "roles": ["recruiter"], "region": "EU"}
result = agent.invoke(
{"messages": [{"role": "user", "content": "How is the vector index refreshed?"}]},
context=RequestContext(
subject=claims["sub"],
roles=tuple(claims["roles"]),
region=claims["region"],
),
)
print(result["messages"][-1].content)
Здесь поток управления представляет собой явную графику: классификация намерения, вызов инструментов поиска, при необходимости обращение к человеку, а затем ответ. Функции пунктов контроля и прерываний превращают длительно работающие потоки поддержки в стабильные сессии вместо словарей в оперативной памяти.
Агент 3 — самоуправляемый процесс ввода и получения данных
"""
AGENT 3: Self-managed pipeline: your chunking, your index, your fusion, your rerank.
Chosen when Bedrock KB's fixed options are not enough (custom fusion, per-stage timing,
an abstention gate, or a chunking rule the managed service will not express).
"""
from __future__ import annotations
import time
from typing import Annotated, Any, TypedDict
import boto3
from langchain_aws import BedrockEmbeddings, ChatBedrockConverse
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
from langgraph.graph import END, START, StateGraph
REGION = "eu-west-1"
CONFIDENCE_FLOOR = 0.34
CANDIDATE_K, CONTEXT_K = 20, 4
embeddings = BedrockEmbeddings(
model_id="amazon.titan-embed-text-v2:0",
region_name=REGION,
model_kwargs={"dimensions": 1024, "normalize": True},
)
llm = ChatBedrockConverse(
model="eu.anthropic.claude-sonnet-4-5-20250929-v1:0",
region_name=REGION,
temperature=0,
)
rerank_client = boto3.client("bedrock-agent-runtime", region_name=REGION)
# ---------------------------------------------------------------- ingest
def chunk_policy_document(markdown: str, source: str, acl: list[str], region: str):
"""Structure-aware first, size-capped second. Headings are the natural unit for
policy text; the recursive splitter only intervenes on oversized sections."""
by_heading = MarkdownHeaderTextSplitter(
headers_to_split_on=[("#", "title"), ("##", "section"), ("###", "clause")]
).split_text(markdown)
capper = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200)
chunks: list[Document] = []
for part in capper.split_documents(by_heading):
heading = " > ".join(
v for k, v in part.metadata.items() if k in ("title", "section", "clause")
)
chunks.append(
Document(
# contextual retrieval: the chunk carries its own scope
page_content=f"{heading}\n{part.page_content}",
metadata={**part.metadata, "source": source, "acl": acl, "region": region},
)
)
return chunks
# ---------------------------------------------------------------- state
class State(TypedDict, total=False):
question: str
roles: list[str]
region: str
candidates: list[Document]
context_docs: list[Document]
confidence: float
answer: str
timings: Annotated[dict[str, float], lambda a, b: {**(a or {}), **(b or {})}]
def _ms(name: str, t0: float) -> dict[str, float]:
return {name: round((time.perf_counter() - t0) * 1000, 1)}
# ---------------------------------------------------------------- nodes
def retrieve(state: State, *, store) -> State:
"""Hybrid search with an ACL pre-filter pushed into the index query."""
t0 = time.perf_counter()
docs = store.similarity_search(
state["question"],
k=CANDIDATE_K,
filter={"acl": {"$in": state["roles"]}, "region": {"$in": [state["region"], "GLOBAL"]}},
)
return {"candidates": docs, "timings": _ms("retrieve", t0)}
def rerank(state: State) -> State:
"""Bedrock Rerank: a cross-encoder scoring each (query, chunk) pair jointly."""
t0 = time.perf_counter()
cands = state["candidates"]
if not cands:
return {"context_docs": [], "confidence": 0.0, "timings": _ms("rerank", t0)}
resp = rerank_client.rerank(
queries=[{"type": "TEXT", "textQuery": {"text": state["question"]}}],
sources=[
{"type": "INLINE", "inlineDocumentSource": {
"type": "TEXT", "textDocument": {"text": d.page_content}}}
for d in cands
],
rerankingConfiguration={
"type": "BEDROCK_RERANKING_MODEL",
"bedrockRerankingConfiguration": {
"numberOfResults": CONTEXT_K,
"modelConfiguration": {
"modelArn": f"arn:aws:bedrock:{REGION}::foundation-model/amazon.rerank-v1:0"
},
},
},
)
ranked = resp["results"]
top = [cands[r["index"]] for r in ranked]
return {
"context_docs": top,
"confidence": round(float(ranked[0]["relevanceScore"]), 3),
"timings": _ms("rerank", t0),
}
def generate(state: State) -> State:
"""Abstain below the floor rather than answering from weak context."""
t0 = time.perf_counter()
if not state["context_docs"] or state["confidence"] < CONFIDENCE_FLOOR:
return {
"answer": "I cannot ground an answer for that in a source you are permitted "
"to read. Routing to the document owner.",
"timings": _ms("generate", t0),
}
context = "\n\n".join(
f"[{i}] {d.metadata.get('source')}\n{d.page_content}"
for i, d in enumerate(state["context_docs"], start=1)
)
prompt = ChatPromptTemplate.from_messages([
("system", "Answer only from the numbered context and cite each claim as [n]."),
("human", "Context:\n{context}\n\nQuestion: {question}"),
])
reply = (prompt | llm).invoke({"context": context, "question": state["question"]})
return {"answer": reply.content, "timings": _ms("generate", t0)}
def build_graph(store):
g = StateGraph(State)
g.add_node("retrieve", lambda s: retrieve(s, store=store))
g.add_node("rerank", rerank)
g.add_node("generate", generate)
g.add_edge(START, "retrieve")
g.add_edge("retrieve", "rerank")
g.add_edge("rerank", "generate")
g.add_edge("generate", END)
return g.compile()
В этом варианте агент самостоятельно обрабатывает разбиение данных на части, вызовы функций генерации векторных представлений, хранение векторов и сбор запросов. Команды выбирают его тогда, когда политика сегментации, гибридное объединение лексических и плотных данных или фильтры аренды должны реализовываться как часть основного кода, а не через настройки в консоли.
Выбор между тремя вариантами
Используйте управляемый вариант, когда важна скорость реализации прототипа и объем корпусов данных невелик. Переходите на LangGraph, когда ключевыми факторами являются разветвление процессов, проверка человеком и сохранение информации между этапами. Выбирайте самоуправляемый вариант, когда качество поиска по идентификаторам, правилам доступа или сегментации доменов становится основным критерием продукта.
Оценка, действительно влияющая на качество
Оценка воспроизведения@k и точности@k на замороженном наборе вопросов, а также степень соответствия ответов полученным фрагментам. Сопоставляйте каждое изменение размера фрагмента или параметров генератора с показателями до и после изменения. Отклоняйте варианты, если лучшие фрагменты имеют низкое качество — калиброванное молчание лучше, чем несвязанные выдумки.
Когда не использовать RAG
Не применяйте механизм поиска к фактам, которые уже известны базовой сети, к чисто творческим задачам, к постоянно меняющимся данным, для которых лучше подходят API, а также к сценариям с чрезвычайно низкой задержкой, где невозможен дополнительный запрос. Иногда достаточно улучшить формулировку инструкций или незначительно отрегулировать формат.
RAG — это система с четкими компонентами: качественный ввод данных, надежный поиск информации, строгие инструкции для генерации и измеримые критерии оценки. Три варианта реализации отражают одну и ту же концепцию на трех уровнях контроля. Выбирайте тот уровень, который соответствует вашим потребностям в управлении, а не тот, который кажется наиболее совершенным на диаграмме.
Чек-лист операционной деятельности: (1) один инструмент встраивания для индексации и поиска, (2) метаданные для управления доступом и указание дат с самого начала, (3) гибридный поиск при наличии идентификаторов, (4) переранжирование при важности точности, (5) ключевые вопросы в системе CI, (6) триггеры пересоздания индекса при изменении источников. Рассматривайте плоскость знаний как продукт с ответственными лицами, показателями качества и механизмами возврата к предыдущему состоянию — а не как временный индекс, за которым никто не следит. По мере роста графов агентов измеряйте стоимость обработки данных наряду с качеством ответов; оркестрация, потребляющая в пятнадцать раз больше ресурсов, чем простой чат, не улучшая при этом точности, является регрессией под видом архитектуры. Ведите руководства по действиям при отказах из-за отсутствия результатов, инцидентах с устаревшими индексами и несоответствиях в работе инструментов встраивания, чтобы сотрудники, находящиеся на дежурстве, не вынуждены были импровизировать на основе логов чатов.
Документируйте каждый источник корпуса данных с указанием ответственного лица и частоты обновления; без присмотра они превращаются в источник проблем.
Рекомендуется использовать раннее перекрытие сегментов; применять семантические разделители только тогда, когда оценки покажут, что фиксированные окна неэффективны.
Записывайте идентификаторы запросов вместе с ответами, чтобы плохие ответы можно было отнести к ошибкам получения данных или смещению при генерации.
По возможности разделяйте оценщиков и сеть, отвечающую на запросы, чтобы уменьшить предвзятость из-за самопредпочтения.
Выделяйте отдельный бюджет на использование API для индексации, отдельно от бюджета на интерактивную обработку запросов, чтобы финансовый отдел мог видеть оба показателя.
Размещайте шаблоны промптов версий рядом с результатами построения индекса; изменение промпта без повторной оценки приводит к появлению регрессий.
Для систем с несколькими арендаторами тестируйте, нельзя ли обойти фильтры с помощью вставки промптов, претендующих на роль администратора.
Сохраняйте функцию отключения, которая останавливает генерацию ответов и возвращает только цитаты при снижении показателей точности.
Пересматривайте гибридные веса лексических элементов каждый раз, когда коды продуктов становятся доминирующими в потоке запросов.
Обучайте сотрудников службы поддержки читать панели цитирования перед тем, как передавать заявки о «неправильном боте» на более высокий уровень.
Документируйте каждый источник корпуса с указанием ответственного лица и частоты обновления; без присмотра такие источники начинают постепенно портиться.
Предпочитайте использование метода перекрытия сегментов на ранних этапах; применяйте семантические разделители только тогда, когда оценки покажут, что фиксированные окна неэффективны.
Записывайте идентификаторы запросов вместе с ответами, чтобы плохие ответы можно было отнести к ошибкам загрузки данных или смещению в процессе генерации.
По возможности разделяйте оценщиков и сеть, отвечающую на запросы, чтобы уменьшить предвзятость из-за субъективного отношения к своим работам.
Выделяйте отдельный бюджет на использование API для индексации, отдельно от бюджета на интерактивную обработку запросов, чтобы финансовый отдел мог видеть оба показателя.
Указывайте версии шаблонов запросов рядом с версиями индексов; изменение шаблона без повторной оценки приводит к появлению негативных изменений.
Для систем с несколькими арендаторами тестируйте, что фильтры нельзя обойти с помощью инъекций запросов, притворяющихся запросами от администраторов.
Используйте функцию отключения, которая прекращает генерацию ответов и возвращает только ссылки на источники при снижении уровня точности мониторинга.
Пересматривайте параметры гибридных весов лексических единиц каждый раз, когда коды продуктов становятся доминирующими в потоке запросов.
Обучите сотрудников службы поддержки читать панели с ссылками на источники перед тем, как передавать заявки о «неправильном боте» на более высокий уровень обработки.
Документируйте каждый источник корпуса с указанием ответственного лица и частоты обновления; без присмотра такие источники начинают постепенно ухудшаться.
Стремитесь к перекрытию сегментов на ранних этапах; применяйте семантические разделители только тогда, когда оценки покажут, что фиксированные окна времени неэффективны.
Записывайте идентификаторы запросов вместе с ответами, чтобы плохие ответы можно было отнести к ошибкам получения данных или смещению в процессе генерации.
По возможности разделяйте оценщиков и сеть, отвечающую на запросы, чтобы уменьшить предвзятость из-за субъективного отношения к своим решениям.
Выделяйте отдельный бюджет на использование API для индексации, отдельно от расходов на интерактивную обработку запросов, чтобы финансовый отдел мог видеть оба показателя.
Шаблоны запросов версии рядом с индексными сборками; изменение запросов без повторной оценки приводит к появлению регрессий.
Для многоклиентских систем проверьте, что фильтры нельзя обойти с помощью вставки запросов под предлогом выполнения функций администратора.
Сохраняйте функцию отключения, которая прекращает генерацию и возвращает только цитаты при снижении качества контроля верности.
Пересматривайте гибридные веса лексикона каждый раз, когда коды продуктов становятся доминирующими в потоке запросов.
Обучите сотрудников службы поддержки читать панели цитат перед тем, как передавать заявки о «неправильном боте» на более высокий уровень.
Сохраняйте сопоставленные оценки и показатели отклонений рядом с показателями задержек, чтобы стоимость и качество отображались на одном листе.
Сохраняйте сопоставленные оценки и показатели отклонений рядом с показателями задержек, чтобы стоимость и качество отображались на одном листе.
Сохраняйте сопоставленные оценки и показатели отклонений рядом с показателями задержек, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одной таблице.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одной таблице.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одной таблице.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одной таблице.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одной таблице.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одной таблице.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одной таблице.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одной таблице.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.
Храните сопоставленные оценки и показатели отклонений рядом с задержкой, чтобы стоимость и качество отображались на одном листе.