Skip to content

Commit 9fc51aa

Browse files
committed
Merge remote-tracking branch 'origin/develop' into pyh/feat_mcp_container_develop
# Conflicts: # frontend/app/[locale]/agents/components/McpConfigModal.tsx
2 parents 530b4bd + 74aff62 commit 9fc51aa

File tree

105 files changed

+1900
-3307
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+1900
-3307
lines changed

.editorconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.{js,jsx,ts,tsx,json}]
10+
indent_style = space
11+
indent_size = 2
12+
13+
[*.md]
14+
trim_trailing_whitespace = false

.github/workflows/docker-deploy.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ on:
1515
description: 'runner array in json format (e.g. ["ubuntu-latest"] or ["self-hosted"])'
1616
required: true
1717
default: '[]'
18+
app_version:
19+
description: 'Docker image tag to build and deploy (e.g. v1.7.1)'
20+
required: true
21+
default: 'latest'
22+
type: string
1823

1924
jobs:
2025
build-main:
@@ -23,7 +28,7 @@ jobs:
2328
- name: Checkout code
2429
uses: actions/checkout@v4
2530
- name: Build main application image
26-
run: docker build --build-arg MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple --build-arg APT_MIRROR=tsinghua -t nexent/nexent -f make/main/Dockerfile .
31+
run: docker build --build-arg MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple --build-arg APT_MIRROR=tsinghua -t nexent/nexent:${{ github.event.inputs.app_version }} -t nexent/nexent -f make/main/Dockerfile .
2732

2833
build-data-process:
2934
runs-on: ${{ fromJson(inputs.runner_label_json) }}
@@ -47,23 +52,23 @@ jobs:
4752
GIT_TRACE=1 GIT_CURL_VERBOSE=1 GIT_LFS_LOG=debug git lfs pull
4853
rm -rf .git .gitattributes
4954
- name: Build data process image
50-
run: docker build --build-arg MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple --build-arg APT_MIRROR=tsinghua -t nexent/nexent-data-process -f make/data_process/Dockerfile .
55+
run: docker build --build-arg MIRROR=https://pypi.tuna.tsinghua.edu.cn/simple --build-arg APT_MIRROR=tsinghua -t nexent/nexent-data-process:${{ github.event.inputs.app_version }} -t nexent/nexent-data-process -f make/data_process/Dockerfile .
5156

5257
build-web:
5358
runs-on: ${{ fromJson(inputs.runner_label_json) }}
5459
steps:
5560
- name: Checkout code
5661
uses: actions/checkout@v4
5762
- name: Build web frontend image
58-
run: docker build --build-arg MIRROR=https://registry.npmmirror.com --build-arg APK_MIRROR=tsinghua -t nexent/nexent-web -f make/web/Dockerfile .
63+
run: docker build --build-arg MIRROR=https://registry.npmmirror.com --build-arg APK_MIRROR=tsinghua -t nexent/nexent-web:${{ github.event.inputs.app_version }} -t nexent/nexent-web -f make/web/Dockerfile .
5964

6065
build-docs:
6166
runs-on: ${{ fromJson(inputs.runner_label_json) }}
6267
steps:
6368
- name: Checkout code
6469
uses: actions/checkout@v4
6570
- name: Build docs image
66-
run: docker build --progress=plain -t nexent/nexent-docs -f make/docs/Dockerfile .
71+
run: docker build --progress=plain -t nexent/nexent-docs:${{ github.event.inputs.app_version }} -t nexent/nexent-docs -f make/docs/Dockerfile .
6772

6873
deploy:
6974
runs-on: ${{ fromJson(inputs.runner_label_json) }}
@@ -76,6 +81,9 @@ jobs:
7681
rm -rf $HOME/nexent
7782
mkdir -p $HOME/nexent
7883
cp -r $GITHUB_WORKSPACE/* $HOME/nexent/
84+
- name: Force APP_VERSION to latest in deploy.sh (CI only)
85+
run: |
86+
sed -i 's/APP_VERSION="$(get_app_version)"/APP_VERSION="${{ github.event.inputs.app_version }}"/' $HOME/nexent/docker/deploy.sh
7987
- name: Start docs container
8088
run: |
8189
docker stop nexent-docs 2>/dev/null || true

backend/config_service.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import uvicorn
22
import logging
33
import warnings
4-
import asyncio
54

65
from consts.const import APP_VERSION
76

@@ -12,30 +11,14 @@
1211

1312
from apps.config_app import app
1413
from utils.logging_utils import configure_logging, configure_elasticsearch_logging
15-
from services.tool_configuration_service import initialize_tools_on_startup
14+
1615

1716
configure_logging(logging.INFO)
1817
configure_elasticsearch_logging()
1918
logger = logging.getLogger("config_service")
2019

2120

22-
async def startup_initialization():
23-
"""
24-
Perform initialization tasks during server startup
25-
"""
21+
if __name__ == "__main__":
2622
logger.info("Starting server initialization...")
2723
logger.info(f"APP version is: {APP_VERSION}")
28-
try:
29-
# Initialize tools on startup - service layer handles detailed logging
30-
await initialize_tools_on_startup()
31-
logger.info("Server initialization completed successfully!")
32-
33-
except Exception as e:
34-
logger.error(f"Server initialization failed: {str(e)}")
35-
# Don't raise the exception to allow server to start even if initialization fails
36-
logger.warning("Server will continue to start despite initialization issues")
37-
38-
39-
if __name__ == "__main__":
40-
asyncio.run(startup_initialization())
4124
uvicorn.run(app, host="0.0.0.0", port=5010, log_level="info")

backend/runtime_service.py

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import uvicorn
22
import logging
33
import warnings
4-
import asyncio
54

65
from consts.const import APP_VERSION
76

@@ -12,31 +11,16 @@
1211

1312
from apps.runtime_app import app
1413
from utils.logging_utils import configure_logging, configure_elasticsearch_logging
15-
from services.tool_configuration_service import initialize_tools_on_startup
14+
1615

1716
configure_logging(logging.INFO)
1817
configure_elasticsearch_logging()
1918
logger = logging.getLogger("runtime_service")
2019

2120

22-
async def startup_initialization():
23-
"""
24-
Perform initialization tasks during server startup
25-
"""
21+
if __name__ == "__main__":
2622
logger.info("Starting server initialization...")
2723
logger.info(f"APP version is: {APP_VERSION}")
28-
try:
29-
# Initialize tools on startup - service layer handles detailed logging
30-
await initialize_tools_on_startup()
31-
logger.info("Server initialization completed successfully!")
32-
except Exception as e:
33-
logger.error(f"Server initialization failed: {str(e)}")
34-
# Don't raise the exception to allow server to start even if initialization fails
35-
logger.warning("Server will continue to start despite initialization issues")
36-
37-
38-
if __name__ == "__main__":
39-
asyncio.run(startup_initialization())
4024
uvicorn.run(app, host="0.0.0.0", port=5014, log_level="info")
4125

4226

backend/services/tool_configuration_service.py

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import jsonref
1212
from mcpadapt.smolagents_adapter import _sanitize_function_name
1313

14-
from consts.const import DEFAULT_USER_ID, LOCAL_MCP_SERVER, DATA_PROCESS_SERVICE
14+
from consts.const import LOCAL_MCP_SERVER, DATA_PROCESS_SERVICE
1515
from consts.exceptions import MCPConnectionError, ToolExecutionException, NotFoundException
1616
from consts.model import ToolInstanceInfoRequest, ToolInfo, ToolSourceEnum, ToolValidateRequest
1717
from database.remote_mcp_db import get_mcp_records_by_tenant, get_mcp_server_by_name_and_tenant
@@ -22,7 +22,6 @@
2222
update_tool_table_from_scan_tool_list,
2323
search_last_tool_instance_by_tool_id,
2424
)
25-
from database.user_tenant_db import get_all_tenant_ids
2625
from services.file_management_service import get_llm_model
2726
from services.vectordatabase_service import get_embedding_model, get_vector_db_core
2827
from services.tenant_config_service import get_selected_knowledge_list, build_knowledge_name_mapping
@@ -368,71 +367,6 @@ async def list_all_tools(tenant_id: str):
368367
return formatted_tools
369368

370369

371-
async def initialize_tools_on_startup():
372-
"""
373-
Initialize and scan all tools during server startup for all tenants
374-
375-
This function scans all available tools (local, LangChain, and MCP)
376-
and updates the database with the latest tool information for all tenants.
377-
"""
378-
379-
logger.info("Starting tool initialization on server startup...")
380-
381-
try:
382-
# Get all tenant IDs from the database
383-
tenant_ids = get_all_tenant_ids()
384-
385-
if not tenant_ids:
386-
logger.warning("No tenants found in database, skipping tool initialization")
387-
return
388-
389-
logger.info(f"Found {len(tenant_ids)} tenants: {tenant_ids}")
390-
391-
total_tools = 0
392-
successful_tenants = 0
393-
failed_tenants = []
394-
395-
# Process each tenant
396-
for tenant_id in tenant_ids:
397-
try:
398-
logger.info(f"Initializing tools for tenant: {tenant_id}")
399-
400-
# Add timeout to prevent hanging during startup
401-
try:
402-
await asyncio.wait_for(
403-
update_tool_list(tenant_id=tenant_id, user_id=DEFAULT_USER_ID),
404-
timeout=60.0 # 60 seconds timeout per tenant
405-
)
406-
407-
# Get the count of tools for this tenant
408-
tools_info = query_all_tools(tenant_id)
409-
tenant_tool_count = len(tools_info)
410-
total_tools += tenant_tool_count
411-
successful_tenants += 1
412-
413-
logger.info(f"Tenant {tenant_id}: {tenant_tool_count} tools initialized")
414-
415-
except asyncio.TimeoutError:
416-
logger.error(f"Tool initialization timed out for tenant {tenant_id}")
417-
failed_tenants.append(f"{tenant_id} (timeout)")
418-
419-
except Exception as e:
420-
logger.error(f"Tool initialization failed for tenant {tenant_id}: {str(e)}")
421-
failed_tenants.append(f"{tenant_id} (error: {str(e)})")
422-
423-
# Log final results
424-
logger.info("Tool initialization completed!")
425-
logger.info(f"Total tools available across all tenants: {total_tools}")
426-
logger.info(f"Successfully processed: {successful_tenants}/{len(tenant_ids)} tenants")
427-
428-
if failed_tenants:
429-
logger.warning(f"Failed tenants: {', '.join(failed_tenants)}")
430-
431-
except Exception as e:
432-
logger.error(f"❌ Tool initialization failed: {str(e)}")
433-
raise
434-
435-
436370
def load_last_tool_config_impl(tool_id: int, tenant_id: str, user_id: str):
437371
"""
438372
Load the last tool configuration for a given tool ID

backend/utils/llm_utils.py

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,37 @@ def _process_thinking_tokens(
1818
) -> bool:
1919
"""
2020
Process tokens to filter out thinking content between <think> and </think> tags.
21+
Handles cases where providers only send a closing tag or mix reasoning_content.
2122
"""
22-
if is_thinking:
23-
return THINK_END_PATTERN not in new_token
23+
# Check for end tag first, as it might appear in the same token as start tag
24+
if THINK_END_PATTERN in new_token:
25+
# If we were never in think mode, treat everything accumulated so far as reasoning and clear it
26+
if not is_thinking:
27+
token_join.clear()
28+
if callback:
29+
callback("") # clear any previously streamed reasoning content
30+
31+
# Exit thinking mode and only keep content after </think>
32+
_, _, after_end = new_token.partition(THINK_END_PATTERN)
33+
is_thinking = False
34+
new_token = after_end
35+
# Continue processing the remaining content in this token
2436

37+
# Check for start tag (after processing end tag, in case both are in the same token)
2538
if THINK_START_PATTERN in new_token:
39+
# Drop any content before <think> and switch to thinking mode
40+
_, _, after_start = new_token.partition(THINK_START_PATTERN)
41+
new_token = after_start
42+
is_thinking = True
43+
44+
if is_thinking:
45+
# Still inside thinking content; ignore until we exit
2646
return True
2747

28-
token_join.append(new_token)
29-
if callback:
30-
callback("".join(token_join))
48+
if new_token:
49+
token_join.append(new_token)
50+
if callback:
51+
callback("".join(token_join))
3152

3253
return False
3354

@@ -46,8 +67,8 @@ def call_llm_for_system_prompt(
4667

4768
llm = OpenAIModel(
4869
model_id=get_model_name_from_config(llm_model_config) if llm_model_config else "",
49-
api_base=llm_model_config.get("base_url", ""),
50-
api_key=llm_model_config.get("api_key", ""),
70+
api_base=llm_model_config.get("base_url", "") if llm_model_config else "",
71+
api_key=llm_model_config.get("api_key", "") if llm_model_config else "",
5172
temperature=0.3,
5273
top_p=0.95,
5374
)
@@ -65,16 +86,38 @@ def call_llm_for_system_prompt(
6586
current_request = llm.client.chat.completions.create(stream=True, **completion_kwargs)
6687
token_join: List[str] = []
6788
is_thinking = False
89+
reasoning_content_seen = False
90+
content_tokens_seen = 0
6891
for chunk in current_request:
69-
new_token = chunk.choices[0].delta.content
92+
delta = chunk.choices[0].delta
93+
reasoning_content = getattr(delta, "reasoning_content", None)
94+
new_token = delta.content
95+
96+
# Note: reasoning_content is separate metadata and doesn't affect content filtering
97+
# We only filter content based on <think> tags in delta.content
98+
if reasoning_content:
99+
reasoning_content_seen = True
100+
logger.debug("Received reasoning_content (metadata only, not filtering content)")
101+
102+
# Process content token if it exists
70103
if new_token is not None:
104+
content_tokens_seen += 1
71105
is_thinking = _process_thinking_tokens(
72106
new_token,
73107
is_thinking,
74108
token_join,
75109
callback,
76110
)
77-
return "".join(token_join)
111+
112+
result = "".join(token_join)
113+
if not result and content_tokens_seen > 0:
114+
logger.warning(
115+
"Generated prompt is empty but %d content tokens were processed. "
116+
"This suggests all content was filtered out.",
117+
content_tokens_seen
118+
)
119+
120+
return result
78121
except Exception as exc:
79122
logger.error("Failed to generate prompt from LLM: %s", str(exc))
80123
raise

doc/docs/.vitepress/config.mts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// https://vitepress.dev/reference/site-config
1+
// https://vitepress.dev/reference/site-config
22
import { defineConfig } from "vitepress";
33

44
export default defineConfig({
@@ -89,6 +89,8 @@ export default defineConfig({
8989
text: "Knowledge Base",
9090
link: "/en/user-guide/knowledge-base",
9191
},
92+
{ text: "MCP Tools", link: "/en/user-guide/mcp-tools" },
93+
{ text: "Monitoring & Ops", link: "/en/user-guide/monitor" },
9294
{
9395
text: "Model Management",
9496
link: "/en/user-guide/model-management",
@@ -267,7 +269,7 @@ export default defineConfig({
267269
items: [
268270
{ text: "首页", link: "/zh/user-guide/home-page" },
269271
{ text: "开始问答", link: "/zh/user-guide/start-chat" },
270-
{ text: "快速设置", link: "/zh/user-guide/quick-setup" },
272+
{ text: "快速配置", link: "/zh/user-guide/quick-setup" },
271273
{ text: "智能体空间", link: "/zh/user-guide/agent-space" },
272274
{ text: "智能体市场", link: "/zh/user-guide/agent-market" },
273275
{
@@ -278,6 +280,8 @@ export default defineConfig({
278280
text: "知识库",
279281
link: "/zh/user-guide/knowledge-base",
280282
},
283+
{ text: "MCP工具", link: "/zh/user-guide/mcp-tools" },
284+
{ text: "监控与运维", link: "/zh/user-guide/monitor" },
281285
{ text: "模型管理", link: "/zh/user-guide/model-management" },
282286
{ text: "记忆管理", link: "/zh/user-guide/memory-management" },
283287
{ text: "用户管理", link: "/zh/user-guide/user-management" },

0 commit comments

Comments
 (0)