Skip to content

Commit 18b630e

Browse files
committed
cost-cutting with smaller documents for grader
1 parent 51ef4aa commit 18b630e

3 files changed

Lines changed: 81 additions & 7 deletions

File tree

.env.example

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,12 @@ LANGCHAIN_PROJECT="langchain-opey"
129129
# SelfRAG Retriever Config
130130
VECTOR_STORE_TYPE="chroma" # Currently only chroma is supported
131131

132-
ENDPOINT_RETRIEVER_BATCH_SIZE=8
132+
ENDPOINT_RETRIEVER_BATCH_SIZE=4
133133
ENDPOINT_RETRIEVER_MAX_RETRIES=2
134134
# If there are less than this number of endpoints found for a given retrieval, retry with rewritten question
135135
ENDPOINT_RETRIEVER_RETRY_THRESHOLD=1
136+
# Use a compacted endpoint documentation for grading, improves cost significantly but might slightly reduce accuracy
137+
ENDPOINT_RETRIEVER_COMPACT_GRADING="true"
136138

137139
# Number of conversation tokens at which we trim the messages and summarize the conversation
138140
CONVERSATION_TOKEN_LIMIT=50000

src/agent/components/retrieval/endpoint_retrieval/components/nodes.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import os
22
import json
3+
import re
34

45
from langchain_core.documents import Document
56
from langchain_core.runnables import RunnableConfig
67
from typing import List, Optional
78
from agent.components.retrieval.endpoint_retrieval.components.states import OutputState
89
from agent.components.retrieval.retriever_config import get_retriever
910
from agent.components.retrieval.endpoint_retrieval.components.chains import retrieval_grader, endpoint_question_rewriter
11+
from database.document_schemas import EndpointDocumentSchema
1012
from dotenv import load_dotenv
1113

1214
load_dotenv()
@@ -15,9 +17,11 @@
1517
DEFAULT_BATCH_SIZE = int(os.getenv("ENDPOINT_RETRIEVER_BATCH_SIZE", "5"))
1618
DEFAULT_RETRY_THRESHOLD = int(os.getenv("ENDPOINT_RETRIEVER_RETRY_THRESHOLD", "2"))
1719
DEFAULT_MAX_RETRIES = int(os.getenv("ENDPOINT_RETRIEVER_MAX_RETRIES", "2"))
20+
# Enable compact grading format by default (significant token savings)
21+
DEFAULT_USE_COMPACT_GRADING = os.getenv("ENDPOINT_RETRIEVER_COMPACT_GRADING", "true").lower() == "true"
1822

1923

20-
def get_retrieval_config(config: Optional[RunnableConfig]) -> tuple[int, int, int]:
24+
def get_retrieval_config(config: Optional[RunnableConfig]) -> tuple[int, int, int, bool]:
2125
"""Extract retrieval config from RunnableConfig or use defaults.
2226
2327
Config can be passed via: graph.ainvoke(input, config={"configurable": {"batch_size": 10}})
@@ -27,6 +31,7 @@ def get_retrieval_config(config: Optional[RunnableConfig]) -> tuple[int, int, in
2731
configurable.get("batch_size", DEFAULT_BATCH_SIZE),
2832
configurable.get("retry_threshold", DEFAULT_RETRY_THRESHOLD),
2933
configurable.get("max_retries", DEFAULT_MAX_RETRIES),
34+
configurable.get("use_compact_grading", DEFAULT_USE_COMPACT_GRADING),
3035
)
3136

3237

@@ -59,6 +64,27 @@ def deduplicate_documents(documents: List[Document]) -> List[Document]:
5964
return unique
6065

6166

67+
def get_compact_grading_content(doc: Document) -> str:
68+
"""
69+
Extract compact content for LLM grading to reduce token usage.
70+
71+
Falls back to full content if compact extraction fails.
72+
"""
73+
try:
74+
schema = EndpointDocumentSchema.from_document(doc.page_content, doc.metadata)
75+
return schema.to_grading_content()
76+
except Exception:
77+
# Fallback: use metadata + truncated content
78+
meta = doc.metadata
79+
parts = [
80+
f"{meta.get('method', '')} {meta.get('path', '')}",
81+
f"Summary: {meta.get('summary', '')}" if meta.get('summary') else "",
82+
f"Tags: {meta.get('tags', '')}" if meta.get('tags') else "",
83+
]
84+
compact = "\n".join(p for p in parts if p)
85+
return compact if compact.strip() else doc.page_content[:500]
86+
87+
6288
async def retrieve_endpoints(state, config: RunnableConfig = None):
6389
"""
6490
Retrieve documents
@@ -71,7 +97,7 @@ async def retrieve_endpoints(state, config: RunnableConfig = None):
7197
state (dict): New key added to state, documents, that contains retrieved documents
7298
"""
7399
print("---RETRIEVE ENDPOINTS---")
74-
batch_size, _, _ = get_retrieval_config(config)
100+
batch_size, _, _, _ = get_retrieval_config(config)
75101
retriever = get_endpoint_retriever(batch_size)
76102

77103
rewritten_question = state.get("rewritten_question", "")
@@ -123,13 +149,24 @@ async def grade_documents(state, config: RunnableConfig = None):
123149
"""
124150

125151
print("---CHECK DOCUMENT RELEVANCE TO QUESTION---")
126-
_, retry_threshold, _ = get_retrieval_config(config)
152+
_, retry_threshold, _, use_compact_grading = get_retrieval_config(config)
127153

128154
question = state["question"]
129155
documents = state["documents"]
130156

157+
# Build grading inputs - use compact format to reduce token usage
158+
if use_compact_grading:
159+
grading_inputs = [
160+
{"question": question, "document": get_compact_grading_content(d)}
161+
for d in documents
162+
]
163+
else:
164+
grading_inputs = [
165+
{"question": question, "document": d.page_content}
166+
for d in documents
167+
]
168+
131169
# Batch grade all documents in parallel
132-
grading_inputs = [{"question": question, "document": d.page_content} for d in documents]
133170
scores = await retrieval_grader.abatch(grading_inputs)
134171

135172
filtered_docs = []

src/database/document_schemas.py

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,45 @@ class EndpointDocumentSchema:
5050
tags: List[str] = field(default_factory=list)
5151

5252
def to_document_content(self) -> str:
53-
"""Convert schema to document content format"""
53+
"""Convert schema to document content format (full version for final output)."""
5454
import json
5555
# Format matching the original script's structure
5656
path_content = {self.path: {self.method.lower(): self.details}}
5757
return json.dumps(path_content)
5858

59+
def to_grading_content(self) -> str:
60+
"""
61+
Convert schema to compact format for LLM grading (token-efficient).
62+
63+
Extracts only the fields needed for relevance assessment:
64+
- summary, description (truncated), tags, parameters overview
65+
"""
66+
import re
67+
68+
summary = self.details.get("summary", "")
69+
description = self.details.get("description", "")
70+
71+
# Strip HTML tags and truncate description
72+
description_clean = re.sub(r'<[^>]+>', ' ', description)
73+
description_clean = re.sub(r'\s+', ' ', description_clean).strip()
74+
# Keep first 200 chars of description for context
75+
if len(description_clean) > 200:
76+
description_clean = description_clean[:200] + "..."
77+
78+
# Extract parameter names only (not full schemas)
79+
params = self.details.get("parameters", [])
80+
param_names = [p.get("name", "") for p in params if isinstance(p, dict) and p.get("in") != "body"]
81+
82+
# Build compact representation
83+
parts = [
84+
f"{self.method.upper()} {self.path}",
85+
f"Summary: {summary}" if summary else "",
86+
f"Tags: {', '.join(self.tags)}" if self.tags else "",
87+
f"Description: {description_clean}" if description_clean else "",
88+
f"Parameters: {', '.join(param_names)}" if param_names else "",
89+
]
90+
return "\n".join(p for p in parts if p)
91+
5992
def to_metadata(self) -> Dict[str, Any]:
6093
"""Convert schema to document metadata"""
6194

@@ -65,7 +98,9 @@ def to_metadata(self) -> Dict[str, Any]:
6598
"method": self.method.upper(),
6699
"operation_id": self.operation_id,
67100
"path": self.path,
68-
"tags": ", ".join(self.tags) if self.tags else ""
101+
"tags": ", ".join(self.tags) if self.tags else "",
102+
# Store summary in metadata for quick access without parsing page_content
103+
"summary": self.details.get("summary", ""),
69104
}
70105

71106
return metadata

0 commit comments

Comments
 (0)