Skip to content

Commit b3e45e9

Browse files
committed
Merge remote-tracking branch 'refs/remotes/origin/develop' into xyc/kb_enforcement
2 parents 0672cbc + fa1f5f4 commit b3e45e9

File tree

74 files changed

+2845
-1279
lines changed

Some content is hidden

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

74 files changed

+2845
-1279
lines changed

backend/apps/file_management_app.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
import re
3+
import base64
34
from http import HTTPStatus
45
from typing import List, Optional
56
from urllib.parse import urlparse, urlunparse, unquote, quote
@@ -149,7 +150,16 @@ async def process_files(
149150
@file_management_config_router.get("/download/{object_name:path}")
150151
async def get_storage_file(
151152
object_name: str = PathParam(..., description="File object name"),
152-
download: str = Query("ignore", description="How to get the file"),
153+
download: str = Query(
154+
"ignore",
155+
description=(
156+
"How to get the file: "
157+
"'ignore' (default, return file info), "
158+
"'stream' (return file stream), "
159+
"'redirect' (redirect to download URL), "
160+
"'base64' (return base64-encoded content for images)."
161+
),
162+
),
153163
expires: int = Query(3600, description="URL validity period (seconds)"),
154164
filename: Optional[str] = Query(None, description="Original filename for download (optional)")
155165
):
@@ -192,6 +202,28 @@ async def get_storage_file(
192202
"ETag": f'"{object_name}"',
193203
}
194204
)
205+
elif download == "base64":
206+
# Return base64 encoded file content (primarily for images)
207+
file_stream, content_type = await get_file_stream_impl(object_name=object_name)
208+
try:
209+
data = file_stream.read()
210+
except Exception as exc:
211+
logger.error("Failed to read file stream for base64: %s", str(exc))
212+
raise HTTPException(
213+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
214+
detail="Failed to read file content for base64 encoding",
215+
)
216+
217+
base64_content = base64.b64encode(data).decode("utf-8")
218+
return JSONResponse(
219+
status_code=HTTPStatus.OK,
220+
content={
221+
"success": True,
222+
"base64": base64_content,
223+
"content_type": content_type,
224+
"object_name": object_name,
225+
},
226+
)
195227
else:
196228
# return file metadata
197229
return await get_file_url_impl(object_name=object_name, expires=expires)

backend/services/conversation_management_service.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from typing import Any, Dict, List, Optional
66

77
from jinja2 import StrictUndefined, Template
8-
from smolagents import OpenAIServerModel
98

109
from consts.const import LANGUAGE, MODEL_CONFIG_MAPPING, MESSAGE_ROLE, DEFAULT_EN_TITLE, DEFAULT_ZH_TITLE
1110
from consts.model import AgentRequest, ConversationResponse, MessageRequest, MessageUnit
@@ -27,7 +26,8 @@
2726
rename_conversation,
2827
update_message_opinion
2928
)
30-
from nexent.core.utils.observer import ProcessType
29+
from nexent.core.utils.observer import MessageObserver, ProcessType
30+
from nexent.core.models import OpenAIModel
3131
from utils.config_utils import get_model_name_from_config, tenant_config_manager
3232
from utils.prompt_template_utils import get_generate_title_prompt_template
3333
from utils.str_utils import remove_think_blocks
@@ -262,8 +262,8 @@ def call_llm_for_title(content: str, tenant_id: str, language: str = LANGUAGE["Z
262262
model_config = tenant_config_manager.get_model_config(
263263
key=MODEL_CONFIG_MAPPING["llm"], tenant_id=tenant_id)
264264

265-
# Create OpenAIServerModel instance
266-
llm = OpenAIServerModel(
265+
# Create OpenAIModel instance
266+
llm = OpenAIModel(
267267
model_id=get_model_name_from_config(model_config) if model_config.get("model_name") else "",
268268
api_base=model_config.get("base_url", ""),
269269
api_key=model_config.get("api_key", ""),

backend/services/vectordatabase_service.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,16 +1001,16 @@ async def summary_index_name(self,
10011001
StreamingResponse containing the generated summary
10021002
"""
10031003
try:
1004+
if not tenant_id:
1005+
raise Exception("Tenant ID is required for summary generation.")
1006+
10041007
from utils.document_vector_utils import (
10051008
process_documents_for_clustering,
10061009
kmeans_cluster_documents,
10071010
summarize_clusters_map_reduce,
10081011
merge_cluster_summaries
10091012
)
10101013

1011-
if not tenant_id:
1012-
raise Exception("Tenant ID is required for summary generation.")
1013-
10141014
# Use new Map-Reduce approach
10151015
sample_count = min(batch_size // 5, 200) # Sample reasonable number of documents
10161016

backend/utils/document_vector_utils.py

Lines changed: 23 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@
1414

1515
import numpy as np
1616
from jinja2 import Template, StrictUndefined
17-
from nexent.vector_database.base import VectorDatabaseCore
1817
from sklearn.cluster import KMeans
1918
from sklearn.metrics import silhouette_score
2019
from sklearn.metrics.pairwise import cosine_similarity
2120

2221
from consts.const import LANGUAGE
22+
from database.model_management_db import get_model_by_model_id
23+
from nexent.core.utils.observer import MessageObserver
24+
from nexent.core.models import OpenAIModel
25+
from nexent.vector_database.base import VectorDatabaseCore
26+
from utils.llm_utils import call_llm_for_system_prompt
2327
from utils.prompt_template_utils import (
2428
get_document_summary_prompt_template,
2529
get_cluster_summary_reduce_prompt_template,
@@ -568,37 +572,22 @@ def summarize_document(document_content: str, filename: str, language: str = LAN
568572

569573
# Call LLM if model_id and tenant_id are provided
570574
if model_id and tenant_id:
571-
from smolagents import OpenAIServerModel
572-
from database.model_management_db import get_model_by_model_id
573-
from utils.config_utils import get_model_name_from_config
574-
from consts.const import MESSAGE_ROLE
575-
575+
576576
# Get model configuration
577577
llm_model_config = get_model_by_model_id(model_id=model_id, tenant_id=tenant_id)
578578
if not llm_model_config:
579579
logger.warning(f"No model configuration found for model_id: {model_id}, tenant_id: {tenant_id}")
580580
return f"[Document Summary: {filename}] (max {max_words} words) - Content: {document_content[:200]}..."
581-
582-
# Create LLM instance
583-
llm = OpenAIServerModel(
584-
model_id=get_model_name_from_config(llm_model_config) if llm_model_config else "",
585-
api_base=llm_model_config.get("base_url", ""),
586-
api_key=llm_model_config.get("api_key", ""),
587-
temperature=0.3,
588-
top_p=0.95
581+
582+
document_summary = call_llm_for_system_prompt(
583+
model_id=model_id,
584+
user_prompt=user_prompt,
585+
system_prompt=system_prompt,
586+
callback=None,
587+
tenant_id=tenant_id
589588
)
590-
591-
# Build messages
592-
messages = [
593-
{"role": MESSAGE_ROLE["SYSTEM"], "content": system_prompt},
594-
{"role": MESSAGE_ROLE["USER"], "content": user_prompt}
595-
]
596-
597-
# Call LLM, allow more tokens for generation
598-
response = llm(messages, max_tokens=max_words * 2)
599-
if not response or not response.content:
600-
return ""
601-
return response.content.strip()
589+
590+
return (document_summary or "").strip()
602591
else:
603592
# Fallback to placeholder if no model configuration
604593
logger.warning("No model_id or tenant_id provided, using placeholder summary")
@@ -642,10 +631,6 @@ def summarize_cluster(document_summaries: List[str], language: str = LANGUAGE["Z
642631

643632
# Call LLM if model_id and tenant_id are provided
644633
if model_id and tenant_id:
645-
from smolagents import OpenAIServerModel
646-
from database.model_management_db import get_model_by_model_id
647-
from utils.config_utils import get_model_name_from_config
648-
from consts.const import MESSAGE_ROLE
649634

650635
# Get model configuration
651636
llm_model_config = get_model_by_model_id(model_id=model_id, tenant_id=tenant_id)
@@ -654,25 +639,15 @@ def summarize_cluster(document_summaries: List[str], language: str = LANGUAGE["Z
654639
return f"[Cluster Summary] (max {max_words} words) - Based on {len(document_summaries)} documents"
655640

656641
# Create LLM instance
657-
llm = OpenAIServerModel(
658-
model_id=get_model_name_from_config(llm_model_config) if llm_model_config else "",
659-
api_base=llm_model_config.get("base_url", ""),
660-
api_key=llm_model_config.get("api_key", ""),
661-
temperature=0.3,
662-
top_p=0.95
642+
cluster_summary = call_llm_for_system_prompt(
643+
model_id=model_id,
644+
user_prompt=user_prompt,
645+
system_prompt=system_prompt,
646+
callback=None,
647+
tenant_id=tenant_id
663648
)
664-
665-
# Build messages
666-
messages = [
667-
{"role": MESSAGE_ROLE["SYSTEM"], "content": system_prompt},
668-
{"role": MESSAGE_ROLE["USER"], "content": user_prompt}
669-
]
670-
671-
# Call LLM
672-
response = llm(messages, max_tokens=max_words * 2) # Allow more tokens for generation
673-
if not response or not response.content:
674-
return ""
675-
return response.content.strip()
649+
650+
return (cluster_summary or "").strip()
676651
else:
677652
# Fallback to placeholder if no model configuration
678653
logger.warning("No model_id or tenant_id provided, using placeholder summary")

backend/utils/llm_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import logging
22
from typing import Callable, List, Optional
33

4-
from smolagents import OpenAIServerModel
5-
64
from consts.const import MESSAGE_ROLE, THINK_END_PATTERN, THINK_START_PATTERN
75
from database.model_management_db import get_model_by_model_id
6+
from nexent.core.utils.observer import MessageObserver
7+
from nexent.core.models import OpenAIModel
88
from utils.config_utils import get_model_name_from_config
99

1010
logger = logging.getLogger("llm_utils")
@@ -44,7 +44,7 @@ def call_llm_for_system_prompt(
4444
"""
4545
llm_model_config = get_model_by_model_id(model_id=model_id, tenant_id=tenant_id)
4646

47-
llm = OpenAIServerModel(
47+
llm = OpenAIModel(
4848
model_id=get_model_name_from_config(llm_model_config) if llm_model_config else "",
4949
api_base=llm_model_config.get("base_url", ""),
5050
api_key=llm_model_config.get("api_key", ""),

doc/docs/en/deployment/upgrade-guide.md

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,62 @@
22

33
## 🚀 Upgrade Overview
44

5-
Follow these four steps to upgrade Nexent safely:
5+
Follow these steps to upgrade Nexent safely:
66

7-
1. Clean up existing containers and images
8-
2. Pull the latest code and run the deployment script
9-
3. Apply database migrations
10-
4. Verify the deployment in your browser
7+
1. Pull the latest code
8+
2. Execute the upgrade script
9+
3. Open the site to confirm service availability
1110

1211
---
1312

14-
## 🧹 Step 1: Clean up old images
13+
## 🔄 Step 1: Update Code
1514

16-
Remove cached resources to avoid conflicts when redeploying:
15+
Before updating, record the current deployment version and data directory information.
16+
17+
- Current Deployment Version Location: APP_VERSION in backend/consts/const.py
18+
- Data Directory Location: ROOT_DIR in docker/.env
19+
20+
**Code downloaded via git**
21+
22+
Update the code using git commands:
23+
24+
```bash
25+
git pull
26+
```
27+
28+
**Code downloaded via ZIP package or other means**
29+
30+
1. Re-download the latest code from GitHub and extract it.
31+
2. If it exists, copy the deploy.options file from the docker directory of your previous deployment script directory to the docker directory of the new code directory. (If the file doesn't exist, you can ignore this step).
32+
33+
## 🔄 Step 2: Execute the Upgrade
34+
35+
Navigate to the docker directory of the updated code and run the upgrade script:
36+
37+
```bash
38+
bash upgrade.sh
39+
```
40+
41+
If deploy.options is missing, the script will prompt you to manually enter configuration details from the previous deployment, such as the current version and data directory. Enter the information you recorded earlier.
42+
43+
>💡 Tip
44+
> The default scenario is quick deployment, which uses .env.example.
45+
> If you need to configure voice models (STT/TTS), please add the relevant variables to .env.example in advance. We will provide a front-end configuration interface as soon as possible.
46+
47+
48+
## 🌐 Step 3: Verify the deployment
49+
50+
After deployment:
51+
52+
1. Open `http://localhost:3000` in your browser.
53+
2. Review the [User Guide](https://doc.nexent.tech/en/user-guide/home-page) to validate agent functionality.
54+
55+
56+
## Optional Operations
57+
58+
### 🧹 Clean Up Old Version Images
59+
60+
If images were not updated correctly, you can clean up old containers and images before upgrading:
1761

1862
```bash
1963
# Stop and remove existing containers
@@ -38,24 +82,9 @@ docker system prune -af
3882
3983
---
4084
41-
## 🔄 Step 2: Update code and redeploy
42-
43-
```bash
44-
git pull
45-
cd nexent/docker
46-
cp .env.example .env
47-
bash deploy.sh
48-
```
49-
50-
> 💡 Tip
51-
> - `.env.example` works for default deployments.
52-
> - Configure speech models (STT/TTS) in `.env` when needed. A frontend configuration flow is coming soon.
53-
54-
---
55-
56-
## 🗄️ Step 3: Apply database migrations
85+
## 🗄️ Manual Database Update
5786
58-
Run the SQL scripts shipped with each release to keep your schema up to date.
87+
If some SQL files fail to execute during the upgrade, you can perform the update manually.
5988
6089
### ✅ Method A: Use a SQL editor (recommended)
6190
@@ -68,8 +97,8 @@ Run the SQL scripts shipped with each release to keep your schema up to date.
6897
- Password
6998
3. Test the connection. When successful, you should see tables under the `nexent` schema.
7099
4. Open a new query window.
71-
5. Navigate to `/nexent/docker/sql`. Each file contains one migration script with its release date in the filename.
72-
6. Execute every script dated after your previous deployment, in chronological order.
100+
5. Navigate to the /nexent/docker/sql directory and open the failed SQL file(s) to view the script.
101+
6. Execute the failed SQL file(s) and any subsequent version SQL files in order.
73102
74103
> ⚠️ Important
75104
> - Always back up the database first, especially in production.
@@ -97,14 +126,12 @@ Run the SQL scripts shipped with each release to keep your schema up to date.
97126
3. Execute SQL files sequentially (host machine example):
98127
99128
```bash
100-
# Example: If today is November 6th and your last update was on October 20th,
101-
# and there are two new files 1030-update.sql and 1105-update.sql,
102129
# execute the following commands (please replace the placeholders with your actual values)
103-
docker exec -i nexent-postgresql psql -U [YOUR_POSTGRES_USER] -d [YOUR_POSTGRES_DB] < ./sql/1030-update.sql
104-
docker exec -i nexent-postgresql psql -U [YOUR_POSTGRES_USER] -d [YOUR_POSTGRES_DB] < ./sql/1105-update.sql
130+
docker exec -i nexent-postgresql psql -U [YOUR_POSTGRES_USER] -d [YOUR_POSTGRES_DB] < ./sql/v1.1.1_1030-update.sql
131+
docker exec -i nexent-postgresql psql -U [YOUR_POSTGRES_USER] -d [YOUR_POSTGRES_DB] < ./sql/v1.1.2_1105-update.sql
105132
```
106133
107-
Execute the scripts in chronological order based on your deployment date.
134+
Execute the corresponding scripts for your deployment versions in version order.
108135
109136
> 💡 Tips
110137
> - Load environment variables first if they are defined in `.env`:
@@ -126,14 +153,3 @@ Run the SQL scripts shipped with each release to keep your schema up to date.
126153
> ```bash
127154
> docker exec -i nexent-postgres pg_dump -U [YOUR_POSTGRES_USER] [YOUR_POSTGRES_DB] > backup_$(date +%F).sql
128155
> ```
129-
130-
---
131-
132-
## 🌐 Step 4: Verify the deployment
133-
134-
After deployment:
135-
136-
1. Open `http://localhost:3000` in your browser.
137-
2. Review the [User Guide](https://doc.nexent.tech/en/user-guide/home-page) to validate agent functionality.
138-
139-

0 commit comments

Comments
 (0)