Skip to content

Commit 78a771e

Browse files
authored
✨ Add adapt to deep thinking model.
2 parents d4ba05f + 99cf9b2 commit 78a771e

File tree

22 files changed

+1248
-84
lines changed

22 files changed

+1248
-84
lines changed

backend/agents/create_agent_info.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,21 @@
2020
logger = logging.getLogger("create_agent_info")
2121

2222
async def create_model_config_list(tenant_id):
23-
main_model_config = tenant_config_manager.get_model_config(key="LLM_ID", tenant_id=tenant_id)
24-
sub_model_config = tenant_config_manager.get_model_config(key="LLM_SECONDARY_ID", tenant_id=tenant_id)
25-
26-
return [ModelConfig(cite_name="main_model",
27-
api_key=main_model_config.get("api_key", ""),
28-
model_name=get_model_name_from_config(main_model_config) if main_model_config.get("model_name") else "",
29-
url=main_model_config.get("base_url", "")),
23+
main_model_config = tenant_config_manager.get_model_config(key="LLM_ID", tenant_id=tenant_id)
24+
sub_model_config = tenant_config_manager.get_model_config(key="LLM_SECONDARY_ID", tenant_id=tenant_id)
25+
26+
return [ModelConfig(cite_name="main_model",
27+
api_key=main_model_config.get("api_key", ""),
28+
model_name=get_model_name_from_config(main_model_config) if main_model_config.get(
29+
"model_name") else "",
30+
url=main_model_config.get("base_url", ""),
31+
is_deep_thinking=main_model_config.get("is_deep_thinking", False)),
3032
ModelConfig(cite_name="sub_model",
3133
api_key=sub_model_config.get("api_key", ""),
32-
model_name=get_model_name_from_config(sub_model_config) if sub_model_config.get("model_name") else "",
33-
url=sub_model_config.get("base_url", ""))]
34+
model_name=get_model_name_from_config(sub_model_config) if sub_model_config.get(
35+
"model_name") else "",
36+
url=sub_model_config.get("base_url", ""),
37+
is_deep_thinking=sub_model_config.get("is_deep_thinking", False))]
3438

3539

3640
async def create_agent_config(agent_id, tenant_id, user_id, language: str = 'zh'):
@@ -288,10 +292,11 @@ async def create_agent_run_info(agent_id, minio_files, query, history, authoriza
288292

289293
agent_run_info = AgentRunInfo(
290294
query=final_query,
291-
model_config_list= model_list,
295+
model_config_list=model_list,
292296
observer=MessageObserver(lang=language),
293-
agent_config=await create_agent_config(agent_id=agent_id, tenant_id=tenant_id, user_id=user_id, language=language),
294-
mcp_host= mcp_host,
297+
agent_config=await create_agent_config(agent_id=agent_id, tenant_id=tenant_id, user_id=user_id,
298+
language=language),
299+
mcp_host=mcp_host,
295300
history=history,
296301
stop_event=threading.Event()
297302
)

backend/database/db_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class ModelRecord(TableBase):
132132
used_token = Column(Integer, doc="Number of tokens already used by the model in Q&A")
133133
display_name = Column(String(100), doc="Model name directly displayed on the frontend, customized by the user")
134134
connect_status = Column(String(100), doc="Model connectivity status of the latest detection. Optional values: Detecting, Available, Unavailable")
135+
is_deep_thinking = Column(Boolean, doc="Whether the model opens up deep thinking")
135136
tenant_id = Column(String(100), doc="Tenant ID for filtering")
136137
create_time = Column(TIMESTAMP(timezone=False), server_default=func.now(), doc="Creation time, audit field")
137138
delete_flag = Column(String(1), default="N", doc="After the user deletes it on the frontend, the deletion flag will be set to \"Y\" for soft deletion. Optional values: Y/N")

backend/services/conversation_management_service.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
delete_conversation, get_conversation, create_conversation, update_message_opinion
1616

1717
from utils.config_utils import tenant_config_manager,get_model_name_from_config
18+
from utils.str_utils import remove_think_tags, add_no_think_token
1819

1920
logger = logging.getLogger("conversation_management_service")
2021

@@ -250,11 +251,12 @@ def call_llm_for_title(content: str, tenant_id: str) -> str:
250251
"content": prompt_template["SYSTEM_PROMPT"]},
251252
{"role": "user",
252253
"content": user_prompt}]
254+
add_no_think_token(messages)
253255

254256
# Call the model
255257
response = llm(messages, max_tokens=10)
256258

257-
return response.content.strip()
259+
return remove_think_tags(response.content.strip())
258260

259261

260262
def update_conversation_title(conversation_id: int, title: str, user_id: str = None) -> bool:

backend/services/prompt_service.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from utils.auth_utils import get_current_user_info
1616
from fastapi import Header, Request
1717

18+
from utils.str_utils import remove_think_tags, add_no_think_token
19+
1820
# Configure logging
1921
logger = logging.getLogger("prompt_service")
2022

@@ -41,6 +43,7 @@ def call_llm_for_system_prompt(user_prompt: str, system_prompt: str, callback=No
4143
)
4244
messages = [{"role": "system", "content": system_prompt},
4345
{"role": "user", "content": user_prompt}]
46+
add_no_think_token(messages)
4447
try:
4548
completion_kwargs = llm._prepare_completion_kwargs(
4649
messages=messages,
@@ -53,6 +56,7 @@ def call_llm_for_system_prompt(user_prompt: str, system_prompt: str, callback=No
5356
for chunk in current_request:
5457
new_token = chunk.choices[0].delta.content
5558
if new_token is not None:
59+
new_token = remove_think_tags(new_token)
5660
token_join.append(new_token)
5761
current_text = "".join(token_join)
5862
if callback is not None:

backend/utils/str_utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from typing import List
2+
3+
4+
def remove_think_tags(text: str) -> str:
5+
"""
6+
Remove thinking tags from text
7+
8+
Args:
9+
text: Input text that may contain thinking tags
10+
11+
Returns:
12+
str: Text with thinking tags removed
13+
"""
14+
return text.replace("<think>", "").replace("</think>", "")
15+
16+
17+
def add_no_think_token(messages: List[dict]):
18+
if not messages:
19+
return
20+
if messages[-1]["role"] == "user" and "content" in messages[-1]:
21+
messages[-1]["content"] += " /no_think"

docker/deploy.sh

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ install() {
196196
export COMPOSE_PROFILES
197197
echo "📋 Using profiles: $COMPOSE_PROFILES"
198198
fi
199-
199+
200200
# Start infrastructure services
201201
if ! docker-compose -p nexent -f "${COMPOSE_FILE}" up -d $INFRA_SERVICES; then
202202
echo "❌ ERROR Failed to start infrastructure services"
@@ -221,13 +221,13 @@ install() {
221221
export MINIO_ACCESS_KEY
222222
export MINIO_SECRET_KEY
223223
fi
224-
224+
225225
wait_for_elasticsearch_healthy || {
226226
echo "❌ ERROR Elasticsearch health check failed"
227227
ERROR_OCCURRED=1
228228
return 1
229229
}
230-
230+
231231
# Generate Elasticsearch API key and export to environment
232232
generate_elasticsearch_api_key_for_env || {
233233
echo "❌ ERROR Failed to generate Elasticsearch API key"
@@ -364,7 +364,7 @@ wait_for_elasticsearch_healthy() {
364364
sleep 10
365365
retries=$((retries + 1))
366366
done
367-
367+
368368
if [ $retries -eq $max_retries ]; then
369369
echo "⚠️ Warning: Elasticsearch did not become healthy within expected time"
370370
echo " You may need to check the container logs and try again"
@@ -378,13 +378,13 @@ wait_for_elasticsearch_healthy() {
378378
# Function to generate Elasticsearch API key for environment variables (not file)
379379
generate_elasticsearch_api_key_for_env() {
380380
echo "🔑 Generating ELASTICSEARCH_API_KEY for environment..."
381-
381+
382382
# Generate API key
383383
API_KEY_JSON=$(docker-compose -p nexent -f "${COMPOSE_FILE}" exec -T nexent-elasticsearch curl -s -u "elastic:$ELASTIC_PASSWORD" "http://localhost:9200/_security/api_key" -H "Content-Type: application/json" -d '{"name":"nexent_deploy_key","role_descriptors":{"nexent_role":{"cluster":["all"],"index":[{"names":["*"],"privileges":["all"]}]}}}')
384-
384+
385385
# Extract API key
386386
ELASTICSEARCH_API_KEY=$(echo "$API_KEY_JSON" | grep -o '"encoded":"[^"]*"' | awk -F'"' '{print $4}')
387-
387+
388388
if [ -n "$ELASTICSEARCH_API_KEY" ]; then
389389
# Export to environment for docker-compose
390390
export ELASTICSEARCH_API_KEY
@@ -413,14 +413,14 @@ generate_env_for_infrastructure() {
413413
echo "❌ ERROR generate_env.sh not found in docker directory"
414414
return 1
415415
fi
416-
416+
417417
# Make sure the script is executable and run it
418418
chmod +x generate_env.sh
419419
if ./generate_env.sh; then
420420
echo "--------------------------------"
421421
echo ""
422422
echo "✅ Environment file generated successfully for infrastructure mode!"
423-
423+
424424
# Source the generated .env file to make variables available
425425
if [ -f "../.env" ]; then
426426
echo "📁 Sourcing generated .env file..."
@@ -436,7 +436,7 @@ generate_env_for_infrastructure() {
436436
echo "❌ ERROR Failed to generate environment file"
437437
return 1
438438
fi
439-
439+
440440
echo ""
441441
echo "--------------------------------"
442442
echo ""
@@ -614,45 +614,45 @@ echo ""
614614
main_deploy() {
615615
# Start deployment
616616
select_deployment_mode || { echo "❌ Deployment mode selection failed"; exit 1; }
617-
617+
618618
# Special handling for infrastructure mode
619619
if [ "$DEPLOYMENT_MODE" = "infrastructure" ]; then
620620
echo "🏗️ Infrastructure mode detected - preparing infrastructure services..."
621-
621+
622622
# Set up basic environment and permissions first
623623
add_permission || { echo "❌ Permission setup failed"; exit 1; }
624-
624+
625625
# Choose image environment (required for Docker images)
626626
echo "🌐 Selecting image environment for infrastructure services..."
627627
choose_image_env || { echo "❌ Image environment setup failed"; exit 1; }
628-
628+
629629
# Generate MinIO keys first to avoid docker-compose warnings
630630
echo "🔑 Pre-generating MinIO keys to avoid docker-compose warnings..."
631631
generate_minio_ak_sk || { echo "❌ MinIO key generation failed"; exit 1; }
632-
632+
633633
# Export MinIO keys to current environment for docker-compose
634634
export MINIO_ACCESS_KEY
635635
export MINIO_SECRET_KEY
636-
636+
637637
# Start infrastructure services (basic services only)
638638
echo "🔧 Starting infrastructure services..."
639639
INFRA_SERVICES="nexent-elasticsearch nexent-postgresql nexent-minio redis"
640640
if ! docker-compose -p nexent -f "${COMPOSE_FILE}" up -d $INFRA_SERVICES; then
641641
echo "❌ ERROR Failed to start infrastructure services"
642642
exit 1
643643
fi
644-
644+
645645
# Wait for services to be healthy, then generate complete environment
646646
echo "🔑 Generating complete environment file with all keys..."
647647
generate_env_for_infrastructure || { echo "❌ Environment generation failed"; exit 1; }
648-
648+
649649
echo "🎉 Infrastructure deployment completed successfully!"
650650
echo "📦 You can now start the core services manually using dev containers"
651651
echo "📁 Environment file available at: $(cd .. && pwd)/.env"
652652
echo "💡 Use 'source .env' to load environment variables in your development shell"
653653
return 0
654654
fi
655-
655+
656656
# Normal deployment flow for other modes
657657
select_terminal_tool || { echo "❌ Terminal tool configuration failed"; exit 1; }
658658
add_permission || { echo "❌ Permission setup failed"; exit 1; }

docker/init.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ CREATE TABLE IF NOT EXISTS "model_record_t" (
165165
"used_token" int4,
166166
"display_name" varchar(100) COLLATE "pg_catalog"."default",
167167
"connect_status" varchar(100) COLLATE "pg_catalog"."default",
168+
"is_deep_thinking" BOOLEAN DEFAULT FALSE,
168169
"create_time" timestamp(0) DEFAULT CURRENT_TIMESTAMP,
169170
"delete_flag" varchar(1) COLLATE "pg_catalog"."default" DEFAULT 'N'::character varying,
170171
"update_time" timestamp(0) DEFAULT CURRENT_TIMESTAMP,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE nexent.model_record_t
2+
ADD COLUMN is_deep_thinking BOOLEAN DEFAULT FALSE;
3+
COMMENT ON COLUMN nexent.model_record_t.is_deep_thinking IS 'deep thinking switch, true=open, false=close';

frontend/app/[locale]/chat/streaming/chatStreamHandler.tsx

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,7 @@ export const handleStreamResponse = async (
164164
break;
165165

166166
case "model_output_thinking":
167-
// Process thinking content
168-
// If there's no currentStep, create one
167+
// Merge consecutive thinking chunks; create new group only when previous subType is not "thinking"
169168
if (!currentStep) {
170169
currentStep = {
171170
id: `step-thinking-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
@@ -180,34 +179,69 @@ export const handleStreamResponse = async (
180179
};
181180
}
182181

183-
// Ensure contents exists
184-
currentContentText = messageContent;
182+
const shouldAppendThinking =
183+
lastContentType === "model_output" &&
184+
lastModelOutputIndex >= 0 &&
185+
currentStep.contents[lastModelOutputIndex] &&
186+
currentStep.contents[lastModelOutputIndex].subType === "thinking";
185187

186-
// If the last streaming output is thinking content, append
187-
if (lastContentType === "model_output" && lastModelOutputIndex >= 0) {
188-
const modelOutput = currentStep.contents[lastModelOutputIndex];
189-
// Update content directly without prefix check
190-
let newContent = modelOutput.content + messageContent;
191-
// Remove "思考:" prefix if present
192-
const thinkingPrefix = t('chatStreamHandler.thinkingPrefix');
193-
if (newContent.startsWith(thinkingPrefix)) {
194-
newContent = newContent.substring(thinkingPrefix.length);
195-
}
196-
modelOutput.content = newContent;
188+
if (shouldAppendThinking) {
189+
// Append to existing thinking content
190+
currentStep.contents[lastModelOutputIndex].content += messageContent;
197191
} else {
198-
// Otherwise, create new thinking content
192+
// Create a new thinking content group
199193
currentStep.contents.push({
200-
id: `model-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
194+
id: `thinking-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
201195
type: "model_output",
202196
subType: "thinking",
203-
content: currentContentText,
197+
content: messageContent,
198+
expanded: true,
199+
timestamp: Date.now()
200+
});
201+
lastModelOutputIndex = currentStep.contents.length - 1;
202+
}
203+
204+
lastContentType = "model_output";
205+
break;
206+
207+
case "model_output_deep_thinking":
208+
// Consecutive deep_thinking chunks should be combined until a thinking chunk arrives
209+
if (!currentStep) {
210+
currentStep = {
211+
id: `step-thinking-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
212+
title: "AI Thinking",
213+
content: "",
214+
expanded: true,
215+
contents: [],
216+
metrics: "",
217+
thinking: { content: "", expanded: true },
218+
code: { content: "", expanded: true },
219+
output: { content: "", expanded: true }
220+
};
221+
}
222+
223+
const shouldAppendDeep =
224+
lastContentType === "model_output" &&
225+
lastModelOutputIndex >= 0 &&
226+
currentStep.contents[lastModelOutputIndex] &&
227+
currentStep.contents[lastModelOutputIndex].subType === "deep_thinking";
228+
229+
if (shouldAppendDeep) {
230+
// Append to existing deep_thinking content
231+
currentStep.contents[lastModelOutputIndex].content += messageContent;
232+
} else {
233+
// Create a new deep_thinking content group
234+
currentStep.contents.push({
235+
id: `deep-thinking-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
236+
type: "model_output",
237+
subType: "deep_thinking",
238+
content: messageContent,
204239
expanded: true,
205240
timestamp: Date.now()
206241
});
207242
lastModelOutputIndex = currentStep.contents.length - 1;
208243
}
209244

210-
// Update the last processed content type
211245
lastContentType = "model_output";
212246
break;
213247

frontend/app/[locale]/chat/streaming/chatStreamMain.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export function ChatStreamMain({
143143
step.contents.forEach((content: any) => {
144144
const taskMsg = {
145145
type: content.type,
146+
subType: content.subType, // Preserve subType for styling (e.g., deep_thinking)
146147
content: content.content,
147148
id: content.id,
148149
assistantId: message.id,

0 commit comments

Comments
 (0)