Skip to content

Commit 6094546

Browse files
authored
Message truncator hook to manage tokens (#332)
1 parent 91d8ac5 commit 6094546

File tree

9 files changed

+85
-45
lines changed

9 files changed

+85
-45
lines changed

deploy/docker/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ services:
173173
- API_PASSWORD=Admin!123
174174
- OPENAPI_SPEC=/app/resources/crapi-openapi-spec.json
175175
- DEFAULT_MODEL=gpt-4o-mini
176+
- MAX_CONTENT_LENGTH=50000
176177
- CHROMA_HOST=chromadb
177178
- CHROMA_PORT=8000
178179
# - CHATBOT_OPENAI_API_KEY=

deploy/helm/templates/chatbot/config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ data:
2222
MONGO_DB_NAME: {{ .Values.mongodb.config.mongoDbName }}
2323
CHATBOT_OPENAI_API_KEY: {{ .Values.openAIApiKey }}
2424
DEFAULT_MODEL: {{ .Values.chatbot.config.defaultModel | quote }}
25+
MAX_CONTENT_LENGTH: {{ .Values.chatbot.config.maxContentLength | quote }}
2526
CHROMA_HOST: {{ .Values.chromadb.service.name }}
2627
CHROMA_PORT: {{ .Values.chromadb.port | quote }}
2728
API_USER: {{ .Values.chatbot.config.apiUser | quote }}

deploy/helm/values.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ chatbot:
153153
mongoDbDriver: mongodb
154154
secretKey: crapi
155155
defaultModel: gpt-4o-mini
156+
maxContentLength: 50000
156157
chromaPersistDirectory: /app/vectorstore
157158
158159
apiPassword: Admin!123

services/chatbot/.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export TLS_ENABLED=false
22
export SERVER_PORT=5002
33
export WEB_SERVICE=localhost:8888
44
export IDENTITY_SERVICE=localhost:8080
5+
export DB_NAME=crapi
56
export DB_USER=admin
67
export DB_PASSWORD=crapisecretpassword
78
export DB_HOST=localhost
@@ -15,6 +16,7 @@ export [email protected]
1516
export API_PASSWORD=Admin!123
1617
export OPENAPI_SPEC=src/resources/crapi-openapi-spec.json
1718
export DEFAULT_MODEL=gpt-4o-mini
19+
export MAX_CONTENT_LENGTH=50000
1820
export CHROMA_HOST=localhost
1921
export CHROMA_PORT=8000
2022
export CHATBOT_OPENAI_API_KEY=
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import json
2+
from langchain_core.messages import ToolMessage
3+
from .config import Config
4+
5+
INDIVIDUAL_MIN_LENGTH = 100
6+
7+
def collect_long_strings(obj):
8+
field_info = []
9+
def _collect(obj):
10+
if isinstance(obj, dict):
11+
for key, value in obj.items():
12+
if isinstance(value, str) and len(value) > INDIVIDUAL_MIN_LENGTH:
13+
field_info.append({
14+
'length': len(value),
15+
'dict': obj,
16+
'key': key,
17+
})
18+
elif isinstance(value, (dict, list)):
19+
_collect(value)
20+
elif isinstance(obj, list):
21+
for item in obj:
22+
if isinstance(item, (dict, list)):
23+
_collect(item)
24+
25+
_collect(obj)
26+
return field_info
27+
28+
29+
def truncate_by_length(content, max_length):
30+
"""
31+
Truncate JSON content by recursively truncating the longest fields until content is under limit.
32+
Preserves structure and smaller fields with minimum loss of information.
33+
"""
34+
try:
35+
data = json.loads(content)
36+
field_info = sorted(collect_long_strings(data), key=lambda x: x['length'])
37+
38+
cur_length = len(json.dumps(data))
39+
while field_info and cur_length-max_length>0:
40+
longest = field_info.pop()
41+
excess = cur_length - max_length
42+
new_length = max(INDIVIDUAL_MIN_LENGTH, longest['length'] - excess)
43+
cur_length -= longest['length'] - new_length
44+
longest['dict'][longest['key']] = (
45+
longest['dict'][longest['key']][:new_length] +
46+
f"... [TRUNCATED: {longest['length'] - new_length} chars removed]"
47+
)
48+
49+
if cur_length <= max_length:
50+
return json.dumps(data)
51+
except (json.JSONDecodeError, Exception):
52+
pass
53+
54+
return content[:max_length] + "\n... [TRUNCATED]"
55+
56+
57+
def truncate_tool_messages(state):
58+
"""
59+
Modify large tool messages to prevent exceeding model's token limits.
60+
Truncate to a length such that it keeps messages within your token limit.
61+
"""
62+
messages = state.get("messages", [])
63+
modified_messages = []
64+
65+
for i,msg in enumerate(messages):
66+
if isinstance(msg, ToolMessage) and len(msg.content) > Config.MAX_CONTENT_LENGTH:
67+
truncated_msg = msg.model_copy(update={
68+
'content': truncate_by_length(msg.content, Config.MAX_CONTENT_LENGTH)
69+
})
70+
modified_messages.append(truncated_msg)
71+
else:
72+
modified_messages.append(msg)
73+
return {"messages": modified_messages}

services/chatbot/src/chatbot/chat_service.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from uuid import uuid4
22
from langgraph.graph.message import Messages
3-
from .retrieverutils import add_to_chroma_collection
3+
from .retriever_utils import add_to_chroma_collection
44
from .extensions import db
55
from .langgraph_agent import execute_langgraph_agent
66

@@ -30,9 +30,10 @@ async def process_user_message(session_id, user_message, api_key, model_name, us
3030
response = await execute_langgraph_agent(
3131
api_key, model_name, history, user_jwt, session_id
3232
)
33+
print("Session ID", session_id)
34+
print("Messages", history)
3335
print("Response", response)
3436
reply: Messages = response.get("messages", [{}])[-1]
35-
print("Reply", reply.content)
3637
response_message_id = uuid4().int & (1 << 63) - 1
3738
history.append(
3839
{"id": response_message_id, "role": "assistant", "content": reply.content}

services/chatbot/src/chatbot/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ class Config:
1111
SECRET_KEY = os.getenv("SECRET_KEY", "super-secret")
1212
MONGO_URI = MONGO_CONNECTION_URI
1313
DEFAULT_MODEL_NAME = os.getenv("DEFAULT_MODEL", "gpt-4o-mini")
14+
MAX_CONTENT_LENGTH = int(os.getenv("MAX_CONTENT_LENGTH", 50000))
1415
CHROMA_HOST = CHROMA_HOST
1516
CHROMA_PORT = CHROMA_PORT

services/chatbot/src/chatbot/langgraph_agent.py

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,12 @@
1-
import os
21
import textwrap
3-
from typing import Annotated, Sequence, TypedDict
4-
5-
from langchain.agents.agent_toolkits import create_retriever_tool
6-
from langchain.chains import LLMChain, RetrievalQA
7-
from langchain.prompts import PromptTemplate
8-
from langchain.schema import BaseMessage
9-
from langchain.tools import Tool
102
from langchain_community.agent_toolkits import SQLDatabaseToolkit
11-
from langchain_community.agent_toolkits.sql.base import create_sql_agent
12-
from langchain_community.document_loaders import DirectoryLoader, TextLoader
13-
from langchain_community.embeddings import OpenAIEmbeddings
14-
from langchain_community.vectorstores import FAISS # or Chroma, Weaviate, etc.
153
from langchain_openai import ChatOpenAI
16-
from langgraph.graph import MessageGraph, StateGraph
17-
from langgraph.graph.message import add_messages
184
from langgraph.prebuilt import create_react_agent
19-
from chromadb.config import DEFAULT_TENANT, DEFAULT_DATABASE, Settings
20-
from .retrieverutils import get_retriever_tool
5+
from .retriever_utils import get_retriever_tool
216

227
from .extensions import postgresdb
23-
from .config import Config
248
from .mcp_client import get_mcp_client
25-
import chromadb
9+
from .agent_utils import truncate_tool_messages
2610

2711

2812
async def build_langgraph_agent(api_key, model_name, user_jwt):
@@ -71,16 +55,13 @@ async def build_langgraph_agent(api_key, model_name, user_jwt):
7155
tools = mcp_tools + db_tools
7256
retriever_tool = get_retriever_tool(api_key)
7357
tools.append(retriever_tool)
74-
agent_node = create_react_agent(model=llm, tools=tools, prompt=system_prompt)
58+
agent_node = create_react_agent(model=llm, tools=tools, prompt=system_prompt, pre_model_hook=truncate_tool_messages)
7559
return agent_node
7660

7761

7862
async def execute_langgraph_agent(
7963
api_key, model_name, messages, user_jwt, session_id=None
8064
):
8165
agent = await build_langgraph_agent(api_key, model_name, user_jwt)
82-
print("messages", messages)
83-
print("Session ID", session_id)
8466
response = await agent.ainvoke({"messages": messages})
85-
print("Response", response)
8667
return response

services/chatbot/src/chatbot/retrieverutils.py renamed to services/chatbot/src/chatbot/retriever_utils.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,7 @@
1-
import os
2-
import textwrap
3-
from typing import Annotated, Sequence, TypedDict
4-
51
from langchain.agents.agent_toolkits import create_retriever_tool
6-
from langchain.chains import LLMChain, RetrievalQA
7-
from langchain.prompts import PromptTemplate
8-
from langchain.schema import BaseMessage
9-
from langchain.tools import Tool
10-
from langchain_community.agent_toolkits import SQLDatabaseToolkit
11-
from langchain_community.agent_toolkits.sql.base import create_sql_agent
12-
from langchain_community.document_loaders import DirectoryLoader, TextLoader
13-
from langchain_community.vectorstores import FAISS # or Chroma, Weaviate, etc.
14-
from langchain_openai import ChatOpenAI
15-
from langgraph.graph import MessageGraph, StateGraph
16-
from langgraph.graph.message import add_messages
17-
from langgraph.prebuilt import create_react_agent
18-
from chromadb.config import DEFAULT_TENANT, DEFAULT_DATABASE
192
import chromadb
203
from langchain_chroma import Chroma as ChromaClient
21-
22-
from .extensions import postgresdb
234
from .config import Config
24-
from .mcp_client import get_mcp_client
25-
265
from langchain_community.embeddings import OpenAIEmbeddings
276
from langchain_core.documents import Document
287

0 commit comments

Comments
 (0)