union-app-chat-stream 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROJECT_OVERVIEW.md +16 -0
- package/app/.env +44 -44
- package/app/.env.dev +44 -44
- package/app/.env.prod.bj11 +44 -44
- package/app/.env.prod.sh20 +44 -44
- package/app/.env.prod.sz31 +44 -44
- package/app/.env.test.bj12 +44 -44
- package/app/config/env_config.py +6 -0
- package/app/manager/chatstream_manager.py +47 -15
- package/app/models/schemas.py +5 -3
- package/app/service/chat_service.py +116 -2
- package/app/views/view_chatstream.py +62 -5
- package/app/wsgi.py +1 -1
- package/package.json +3 -1
- package/app/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/__pycache__/authenticated_user.cpython-312.pyc +0 -0
- package/app/__pycache__/extensions.cpython-312.pyc +0 -0
- package/app/__pycache__/wsgi.cpython-312.pyc +0 -0
- package/app/config/__pycache__/config_loader.cpython-312.pyc +0 -0
- package/app/config/__pycache__/env_config.cpython-312.pyc +0 -0
- package/app/config/__pycache__/logger_config.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/chatstream_manager.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/prompts.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/runtime_manager.cpython-312.pyc +0 -0
- package/app/manager/__pycache__/toolcall_manager.cpython-312.pyc +0 -0
- package/app/models/__pycache__/schemas.cpython-312.pyc +0 -0
- package/app/service/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/service/__pycache__/chat_service.cpython-312.pyc +0 -0
- package/app/service/__pycache__/llm_service.cpython-312.pyc +0 -0
- package/app/service/__pycache__/rag_service.cpython-312.pyc +0 -0
- package/app/service/__pycache__/tool_call_service.cpython-312.pyc +0 -0
- package/app/service/__pycache__/union_service.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/common_utils.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/debug_context.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/function_utils.cpython-312.pyc +0 -0
- package/app/utils/__pycache__/jwt_utils.cpython-312.pyc +0 -0
- package/app/views/__pycache__/__init__.cpython-312.pyc +0 -0
- package/app/views/__pycache__/view_chatstream.cpython-312.pyc +0 -0
- package/app/views/__pycache__/view_healthcheck.cpython-312.pyc +0 -0
- package/app/views/__pycache__/view_runtime.cpython-312.pyc +0 -0
package/PROJECT_OVERVIEW.md
CHANGED
|
@@ -162,6 +162,22 @@ Deployment files are organized under `deploy/`:
|
|
|
162
162
|
treat knowledge-base "related functions" as routing hints unless code
|
|
163
163
|
explicitly promotes them to executable tools.
|
|
164
164
|
|
|
165
|
+
## Streaming Tool-Call Contract
|
|
166
|
+
|
|
167
|
+
- External tool calls may run for minutes or longer because some tools query
|
|
168
|
+
large data platforms such as Hive. Do not treat a long-running tool call as a
|
|
169
|
+
timeout by default.
|
|
170
|
+
- During tool execution, `/chatstream/v1/chat/stream` emits SSE `heartbeat`
|
|
171
|
+
events every `TOOL_CALL_HEARTBEAT_INTERVAL` seconds. The payload is the normal
|
|
172
|
+
chat response JSON with a `heartbeat` object, including `type`, `tool`,
|
|
173
|
+
`elapsedSeconds`, and `message`.
|
|
174
|
+
- Frontends should listen for both `message` and `heartbeat` SSE events.
|
|
175
|
+
Heartbeat events mean the stream is alive and the current tool is still
|
|
176
|
+
running; they are not model content and should not be rendered as answer text.
|
|
177
|
+
- Tool execution failures return a `message` event with `finish_reason="error"`
|
|
178
|
+
and `errorMsg`. If tool-calling reaches `TOOLS_MAX_ROUNDS`, return
|
|
179
|
+
`finish_reason="error"` and `errorMsg="工具调用轮数达到上限(N轮)"`.
|
|
180
|
+
|
|
165
181
|
## Validation
|
|
166
182
|
|
|
167
183
|
Use the smallest reliable checks that cover the change:
|
package/app/.env
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
# Flask and request authentication
|
|
2
|
-
SECRET_KEY
|
|
2
|
+
SECRET_KEY=__MASKED_FOR_NPM__
|
|
3
3
|
|
|
4
4
|
# External API endpoints
|
|
5
|
-
GET_USE_INFO_URL
|
|
6
|
-
GET_ORG_INFO_URL
|
|
7
|
-
GET_JIRA_INFO_URL
|
|
8
|
-
GET_BIGDATA_URL
|
|
9
|
-
GET_UNION_BASE_URL
|
|
5
|
+
GET_USE_INFO_URL=__MASKED_FOR_NPM__
|
|
6
|
+
GET_ORG_INFO_URL=__MASKED_FOR_NPM__
|
|
7
|
+
GET_JIRA_INFO_URL=__MASKED_FOR_NPM__
|
|
8
|
+
GET_BIGDATA_URL=__MASKED_FOR_NPM__
|
|
9
|
+
GET_UNION_BASE_URL=__MASKED_FOR_NPM__
|
|
10
10
|
|
|
11
11
|
# Legacy external API tokens
|
|
12
|
-
GET_ORG_INFO_URL_TOKEN
|
|
13
|
-
GET_JIRA_INFO_URL_TOKEN
|
|
12
|
+
GET_ORG_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
13
|
+
GET_JIRA_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
14
14
|
|
|
15
15
|
# Authorization and logging
|
|
16
|
-
PERMISSIONS=
|
|
17
|
-
LOG_LEVEL=
|
|
18
|
-
CONSOLE_STDOUT=
|
|
19
|
-
LOG_DIR
|
|
16
|
+
PERMISSIONS=__MASKED_FOR_NPM__
|
|
17
|
+
LOG_LEVEL=__MASKED_FOR_NPM__
|
|
18
|
+
CONSOLE_STDOUT=__MASKED_FOR_NPM__
|
|
19
|
+
LOG_DIR=__MASKED_FOR_NPM__
|
|
20
20
|
|
|
21
21
|
# Runtime environment and JWT
|
|
22
|
-
FLASK_ENV=
|
|
23
|
-
JWT_SECRET_KEY
|
|
24
|
-
JWT_EXPIRATION_SECOND=
|
|
25
|
-
JWT_RENEW_SECOND=
|
|
22
|
+
FLASK_ENV=__MASKED_FOR_NPM__
|
|
23
|
+
JWT_SECRET_KEY=__MASKED_FOR_NPM__
|
|
24
|
+
JWT_EXPIRATION_SECOND=__MASKED_FOR_NPM__
|
|
25
|
+
JWT_RENEW_SECOND=__MASKED_FOR_NPM__
|
|
26
26
|
|
|
27
27
|
# LLM provider
|
|
28
|
-
LLM_URL
|
|
29
|
-
LLM_KEY
|
|
30
|
-
LLM_MODEL=
|
|
31
|
-
LLM_MAX_TOKENS=
|
|
32
|
-
LLM_TEMPERATURE=
|
|
33
|
-
LLM_TOP_P=
|
|
28
|
+
LLM_URL=__MASKED_FOR_NPM__
|
|
29
|
+
LLM_KEY=__MASKED_FOR_NPM__
|
|
30
|
+
LLM_MODEL=__MASKED_FOR_NPM__
|
|
31
|
+
LLM_MAX_TOKENS=__MASKED_FOR_NPM__
|
|
32
|
+
LLM_TEMPERATURE=__MASKED_FOR_NPM__
|
|
33
|
+
LLM_TOP_P=__MASKED_FOR_NPM__
|
|
34
34
|
|
|
35
35
|
# Chat behavior
|
|
36
|
-
SYSTEM_PROMPT
|
|
36
|
+
SYSTEM_PROMPT=__MASKED_FOR_NPM__
|
|
37
37
|
|
|
38
38
|
# Business filter
|
|
39
|
-
FILTER_ENABLED=
|
|
40
|
-
FILTER_ALLOWED_KEYWORDS
|
|
41
|
-
FILTER_REJECTION_MESSAGE
|
|
39
|
+
FILTER_ENABLED=__MASKED_FOR_NPM__
|
|
40
|
+
FILTER_ALLOWED_KEYWORDS=__MASKED_FOR_NPM__
|
|
41
|
+
FILTER_REJECTION_MESSAGE=__MASKED_FOR_NPM__
|
|
42
42
|
|
|
43
43
|
# Tool and conversation settings
|
|
44
|
-
TOOLS_MAX_ROUNDS=
|
|
45
|
-
CONVERSATION_MAX_HISTORY=
|
|
46
|
-
CONVERSATION_TTL=
|
|
44
|
+
TOOLS_MAX_ROUNDS=__MASKED_FOR_NPM__
|
|
45
|
+
CONVERSATION_MAX_HISTORY=__MASKED_FOR_NPM__
|
|
46
|
+
CONVERSATION_TTL=__MASKED_FOR_NPM__
|
|
47
47
|
|
|
48
48
|
# RAG settings
|
|
49
|
-
RAG_ENABLED=
|
|
50
|
-
RAG_KNOWLEDGE_DIR=
|
|
51
|
-
RAG_PERSIST_DIR
|
|
52
|
-
RAG_COLLECTION=
|
|
53
|
-
RAG_EMBEDDING_MODEL=
|
|
54
|
-
RAG_EMBEDDING_MAX_CHARS=
|
|
55
|
-
RAG_EMBEDDING_BATCH_SIZE=
|
|
56
|
-
RAG_TOP_K=
|
|
57
|
-
RAG_SEMANTIC_CANDIDATE_K=
|
|
58
|
-
RAG_CONTEXT_K=
|
|
59
|
-
RAG_EXACT_CONTEXT_K=
|
|
60
|
-
RAG_EXACT_PER_FILE_CONTEXT_K=
|
|
61
|
-
RAG_PER_FILE_CONTEXT_K=
|
|
62
|
-
RAG_CHUNK_SIZE=
|
|
63
|
-
RAG_REBUILD_ON_STARTUP=
|
|
49
|
+
RAG_ENABLED=__MASKED_FOR_NPM__
|
|
50
|
+
RAG_KNOWLEDGE_DIR=__MASKED_FOR_NPM__
|
|
51
|
+
RAG_PERSIST_DIR=__MASKED_FOR_NPM__
|
|
52
|
+
RAG_COLLECTION=__MASKED_FOR_NPM__
|
|
53
|
+
RAG_EMBEDDING_MODEL=__MASKED_FOR_NPM__
|
|
54
|
+
RAG_EMBEDDING_MAX_CHARS=__MASKED_FOR_NPM__
|
|
55
|
+
RAG_EMBEDDING_BATCH_SIZE=__MASKED_FOR_NPM__
|
|
56
|
+
RAG_TOP_K=__MASKED_FOR_NPM__
|
|
57
|
+
RAG_SEMANTIC_CANDIDATE_K=__MASKED_FOR_NPM__
|
|
58
|
+
RAG_CONTEXT_K=__MASKED_FOR_NPM__
|
|
59
|
+
RAG_EXACT_CONTEXT_K=__MASKED_FOR_NPM__
|
|
60
|
+
RAG_EXACT_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
61
|
+
RAG_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
62
|
+
RAG_CHUNK_SIZE=__MASKED_FOR_NPM__
|
|
63
|
+
RAG_REBUILD_ON_STARTUP=__MASKED_FOR_NPM__
|
package/app/.env.dev
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
# Flask and request authentication
|
|
2
|
-
SECRET_KEY
|
|
2
|
+
SECRET_KEY=__MASKED_FOR_NPM__
|
|
3
3
|
|
|
4
4
|
# External API endpoints
|
|
5
|
-
GET_USE_INFO_URL
|
|
6
|
-
GET_ORG_INFO_URL
|
|
7
|
-
GET_JIRA_INFO_URL
|
|
8
|
-
GET_BIGDATA_URL
|
|
9
|
-
GET_UNION_BASE_URL
|
|
5
|
+
GET_USE_INFO_URL=__MASKED_FOR_NPM__
|
|
6
|
+
GET_ORG_INFO_URL=__MASKED_FOR_NPM__
|
|
7
|
+
GET_JIRA_INFO_URL=__MASKED_FOR_NPM__
|
|
8
|
+
GET_BIGDATA_URL=__MASKED_FOR_NPM__
|
|
9
|
+
GET_UNION_BASE_URL=__MASKED_FOR_NPM__
|
|
10
10
|
|
|
11
11
|
# Legacy external API tokens
|
|
12
|
-
GET_ORG_INFO_URL_TOKEN
|
|
13
|
-
GET_JIRA_INFO_URL_TOKEN
|
|
12
|
+
GET_ORG_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
13
|
+
GET_JIRA_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
14
14
|
|
|
15
15
|
# Authorization and logging
|
|
16
|
-
PERMISSIONS=
|
|
17
|
-
LOG_LEVEL=
|
|
18
|
-
CONSOLE_STDOUT=
|
|
19
|
-
LOG_DIR
|
|
16
|
+
PERMISSIONS=__MASKED_FOR_NPM__
|
|
17
|
+
LOG_LEVEL=__MASKED_FOR_NPM__
|
|
18
|
+
CONSOLE_STDOUT=__MASKED_FOR_NPM__
|
|
19
|
+
LOG_DIR=__MASKED_FOR_NPM__
|
|
20
20
|
|
|
21
21
|
# Runtime environment and JWT
|
|
22
|
-
FLASK_ENV=
|
|
23
|
-
JWT_SECRET_KEY
|
|
24
|
-
JWT_EXPIRATION_SECOND=
|
|
25
|
-
JWT_RENEW_SECOND=
|
|
22
|
+
FLASK_ENV=__MASKED_FOR_NPM__
|
|
23
|
+
JWT_SECRET_KEY=__MASKED_FOR_NPM__
|
|
24
|
+
JWT_EXPIRATION_SECOND=__MASKED_FOR_NPM__
|
|
25
|
+
JWT_RENEW_SECOND=__MASKED_FOR_NPM__
|
|
26
26
|
|
|
27
27
|
# LLM provider
|
|
28
|
-
LLM_URL
|
|
29
|
-
LLM_KEY
|
|
30
|
-
LLM_MODEL=
|
|
31
|
-
LLM_MAX_TOKENS=
|
|
32
|
-
LLM_TEMPERATURE=
|
|
33
|
-
LLM_TOP_P=
|
|
28
|
+
LLM_URL=__MASKED_FOR_NPM__
|
|
29
|
+
LLM_KEY=__MASKED_FOR_NPM__
|
|
30
|
+
LLM_MODEL=__MASKED_FOR_NPM__
|
|
31
|
+
LLM_MAX_TOKENS=__MASKED_FOR_NPM__
|
|
32
|
+
LLM_TEMPERATURE=__MASKED_FOR_NPM__
|
|
33
|
+
LLM_TOP_P=__MASKED_FOR_NPM__
|
|
34
34
|
|
|
35
35
|
# Chat behavior
|
|
36
|
-
SYSTEM_PROMPT
|
|
36
|
+
SYSTEM_PROMPT=__MASKED_FOR_NPM__
|
|
37
37
|
|
|
38
38
|
# Business filter
|
|
39
|
-
FILTER_ENABLED=
|
|
40
|
-
FILTER_ALLOWED_KEYWORDS
|
|
41
|
-
FILTER_REJECTION_MESSAGE
|
|
39
|
+
FILTER_ENABLED=__MASKED_FOR_NPM__
|
|
40
|
+
FILTER_ALLOWED_KEYWORDS=__MASKED_FOR_NPM__
|
|
41
|
+
FILTER_REJECTION_MESSAGE=__MASKED_FOR_NPM__
|
|
42
42
|
|
|
43
43
|
# Tool and conversation settings
|
|
44
|
-
TOOLS_MAX_ROUNDS=
|
|
45
|
-
CONVERSATION_MAX_HISTORY=
|
|
46
|
-
CONVERSATION_TTL=
|
|
44
|
+
TOOLS_MAX_ROUNDS=__MASKED_FOR_NPM__
|
|
45
|
+
CONVERSATION_MAX_HISTORY=__MASKED_FOR_NPM__
|
|
46
|
+
CONVERSATION_TTL=__MASKED_FOR_NPM__
|
|
47
47
|
|
|
48
48
|
# RAG settings
|
|
49
|
-
RAG_ENABLED=
|
|
50
|
-
RAG_KNOWLEDGE_DIR=
|
|
51
|
-
RAG_PERSIST_DIR
|
|
52
|
-
RAG_COLLECTION=
|
|
53
|
-
RAG_EMBEDDING_MODEL=
|
|
54
|
-
RAG_EMBEDDING_MAX_CHARS=
|
|
55
|
-
RAG_EMBEDDING_BATCH_SIZE=
|
|
56
|
-
RAG_TOP_K=
|
|
57
|
-
RAG_SEMANTIC_CANDIDATE_K=
|
|
58
|
-
RAG_CONTEXT_K=
|
|
59
|
-
RAG_EXACT_CONTEXT_K=
|
|
60
|
-
RAG_EXACT_PER_FILE_CONTEXT_K=
|
|
61
|
-
RAG_PER_FILE_CONTEXT_K=
|
|
62
|
-
RAG_CHUNK_SIZE=
|
|
63
|
-
RAG_REBUILD_ON_STARTUP=
|
|
49
|
+
RAG_ENABLED=__MASKED_FOR_NPM__
|
|
50
|
+
RAG_KNOWLEDGE_DIR=__MASKED_FOR_NPM__
|
|
51
|
+
RAG_PERSIST_DIR=__MASKED_FOR_NPM__
|
|
52
|
+
RAG_COLLECTION=__MASKED_FOR_NPM__
|
|
53
|
+
RAG_EMBEDDING_MODEL=__MASKED_FOR_NPM__
|
|
54
|
+
RAG_EMBEDDING_MAX_CHARS=__MASKED_FOR_NPM__
|
|
55
|
+
RAG_EMBEDDING_BATCH_SIZE=__MASKED_FOR_NPM__
|
|
56
|
+
RAG_TOP_K=__MASKED_FOR_NPM__
|
|
57
|
+
RAG_SEMANTIC_CANDIDATE_K=__MASKED_FOR_NPM__
|
|
58
|
+
RAG_CONTEXT_K=__MASKED_FOR_NPM__
|
|
59
|
+
RAG_EXACT_CONTEXT_K=__MASKED_FOR_NPM__
|
|
60
|
+
RAG_EXACT_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
61
|
+
RAG_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
62
|
+
RAG_CHUNK_SIZE=__MASKED_FOR_NPM__
|
|
63
|
+
RAG_REBUILD_ON_STARTUP=__MASKED_FOR_NPM__
|
package/app/.env.prod.bj11
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
# Flask and request authentication
|
|
2
|
-
SECRET_KEY
|
|
2
|
+
SECRET_KEY=__MASKED_FOR_NPM__
|
|
3
3
|
|
|
4
4
|
# External API endpoints
|
|
5
|
-
GET_USE_INFO_URL
|
|
6
|
-
GET_ORG_INFO_URL
|
|
7
|
-
GET_JIRA_INFO_URL
|
|
8
|
-
GET_BIGDATA_URL
|
|
9
|
-
GET_UNION_BASE_URL
|
|
5
|
+
GET_USE_INFO_URL=__MASKED_FOR_NPM__
|
|
6
|
+
GET_ORG_INFO_URL=__MASKED_FOR_NPM__
|
|
7
|
+
GET_JIRA_INFO_URL=__MASKED_FOR_NPM__
|
|
8
|
+
GET_BIGDATA_URL=__MASKED_FOR_NPM__
|
|
9
|
+
GET_UNION_BASE_URL=__MASKED_FOR_NPM__
|
|
10
10
|
|
|
11
11
|
# Legacy external API tokens
|
|
12
|
-
GET_ORG_INFO_URL_TOKEN
|
|
13
|
-
GET_JIRA_INFO_URL_TOKEN
|
|
12
|
+
GET_ORG_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
13
|
+
GET_JIRA_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
14
14
|
|
|
15
15
|
# Authorization and logging
|
|
16
|
-
PERMISSIONS=
|
|
17
|
-
LOG_LEVEL=
|
|
18
|
-
CONSOLE_STDOUT=
|
|
19
|
-
LOG_DIR
|
|
16
|
+
PERMISSIONS=__MASKED_FOR_NPM__
|
|
17
|
+
LOG_LEVEL=__MASKED_FOR_NPM__
|
|
18
|
+
CONSOLE_STDOUT=__MASKED_FOR_NPM__
|
|
19
|
+
LOG_DIR=__MASKED_FOR_NPM__
|
|
20
20
|
|
|
21
21
|
# Runtime environment and JWT
|
|
22
|
-
FLASK_ENV=
|
|
23
|
-
JWT_SECRET_KEY
|
|
24
|
-
JWT_EXPIRATION_SECOND=
|
|
25
|
-
JWT_RENEW_SECOND=
|
|
22
|
+
FLASK_ENV=__MASKED_FOR_NPM__
|
|
23
|
+
JWT_SECRET_KEY=__MASKED_FOR_NPM__
|
|
24
|
+
JWT_EXPIRATION_SECOND=__MASKED_FOR_NPM__
|
|
25
|
+
JWT_RENEW_SECOND=__MASKED_FOR_NPM__
|
|
26
26
|
|
|
27
27
|
# LLM provider
|
|
28
|
-
LLM_URL
|
|
29
|
-
LLM_KEY
|
|
30
|
-
LLM_MODEL=
|
|
31
|
-
LLM_MAX_TOKENS=
|
|
32
|
-
LLM_TEMPERATURE=
|
|
33
|
-
LLM_TOP_P=
|
|
28
|
+
LLM_URL=__MASKED_FOR_NPM__
|
|
29
|
+
LLM_KEY=__MASKED_FOR_NPM__
|
|
30
|
+
LLM_MODEL=__MASKED_FOR_NPM__
|
|
31
|
+
LLM_MAX_TOKENS=__MASKED_FOR_NPM__
|
|
32
|
+
LLM_TEMPERATURE=__MASKED_FOR_NPM__
|
|
33
|
+
LLM_TOP_P=__MASKED_FOR_NPM__
|
|
34
34
|
|
|
35
35
|
# Chat behavior
|
|
36
|
-
SYSTEM_PROMPT
|
|
36
|
+
SYSTEM_PROMPT=__MASKED_FOR_NPM__
|
|
37
37
|
|
|
38
38
|
# Business filter
|
|
39
|
-
FILTER_ENABLED=
|
|
40
|
-
FILTER_ALLOWED_KEYWORDS
|
|
41
|
-
FILTER_REJECTION_MESSAGE
|
|
39
|
+
FILTER_ENABLED=__MASKED_FOR_NPM__
|
|
40
|
+
FILTER_ALLOWED_KEYWORDS=__MASKED_FOR_NPM__
|
|
41
|
+
FILTER_REJECTION_MESSAGE=__MASKED_FOR_NPM__
|
|
42
42
|
|
|
43
43
|
# Tool and conversation settings
|
|
44
|
-
TOOLS_MAX_ROUNDS=
|
|
45
|
-
CONVERSATION_MAX_HISTORY=
|
|
46
|
-
CONVERSATION_TTL=
|
|
44
|
+
TOOLS_MAX_ROUNDS=__MASKED_FOR_NPM__
|
|
45
|
+
CONVERSATION_MAX_HISTORY=__MASKED_FOR_NPM__
|
|
46
|
+
CONVERSATION_TTL=__MASKED_FOR_NPM__
|
|
47
47
|
|
|
48
48
|
# RAG settings
|
|
49
|
-
RAG_ENABLED=
|
|
50
|
-
RAG_KNOWLEDGE_DIR=
|
|
51
|
-
RAG_PERSIST_DIR
|
|
52
|
-
RAG_COLLECTION=
|
|
53
|
-
RAG_EMBEDDING_MODEL=
|
|
54
|
-
RAG_EMBEDDING_MAX_CHARS=
|
|
55
|
-
RAG_EMBEDDING_BATCH_SIZE=
|
|
56
|
-
RAG_TOP_K=
|
|
57
|
-
RAG_SEMANTIC_CANDIDATE_K=
|
|
58
|
-
RAG_CONTEXT_K=
|
|
59
|
-
RAG_EXACT_CONTEXT_K=
|
|
60
|
-
RAG_EXACT_PER_FILE_CONTEXT_K=
|
|
61
|
-
RAG_PER_FILE_CONTEXT_K=
|
|
62
|
-
RAG_CHUNK_SIZE=
|
|
63
|
-
RAG_REBUILD_ON_STARTUP=
|
|
49
|
+
RAG_ENABLED=__MASKED_FOR_NPM__
|
|
50
|
+
RAG_KNOWLEDGE_DIR=__MASKED_FOR_NPM__
|
|
51
|
+
RAG_PERSIST_DIR=__MASKED_FOR_NPM__
|
|
52
|
+
RAG_COLLECTION=__MASKED_FOR_NPM__
|
|
53
|
+
RAG_EMBEDDING_MODEL=__MASKED_FOR_NPM__
|
|
54
|
+
RAG_EMBEDDING_MAX_CHARS=__MASKED_FOR_NPM__
|
|
55
|
+
RAG_EMBEDDING_BATCH_SIZE=__MASKED_FOR_NPM__
|
|
56
|
+
RAG_TOP_K=__MASKED_FOR_NPM__
|
|
57
|
+
RAG_SEMANTIC_CANDIDATE_K=__MASKED_FOR_NPM__
|
|
58
|
+
RAG_CONTEXT_K=__MASKED_FOR_NPM__
|
|
59
|
+
RAG_EXACT_CONTEXT_K=__MASKED_FOR_NPM__
|
|
60
|
+
RAG_EXACT_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
61
|
+
RAG_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
62
|
+
RAG_CHUNK_SIZE=__MASKED_FOR_NPM__
|
|
63
|
+
RAG_REBUILD_ON_STARTUP=__MASKED_FOR_NPM__
|
package/app/.env.prod.sh20
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
# Flask and request authentication
|
|
2
|
-
SECRET_KEY
|
|
2
|
+
SECRET_KEY=__MASKED_FOR_NPM__
|
|
3
3
|
|
|
4
4
|
# External API endpoints
|
|
5
|
-
GET_USE_INFO_URL
|
|
6
|
-
GET_ORG_INFO_URL
|
|
7
|
-
GET_JIRA_INFO_URL
|
|
8
|
-
GET_BIGDATA_URL
|
|
9
|
-
GET_UNION_BASE_URL
|
|
5
|
+
GET_USE_INFO_URL=__MASKED_FOR_NPM__
|
|
6
|
+
GET_ORG_INFO_URL=__MASKED_FOR_NPM__
|
|
7
|
+
GET_JIRA_INFO_URL=__MASKED_FOR_NPM__
|
|
8
|
+
GET_BIGDATA_URL=__MASKED_FOR_NPM__
|
|
9
|
+
GET_UNION_BASE_URL=__MASKED_FOR_NPM__
|
|
10
10
|
|
|
11
11
|
# Legacy external API tokens
|
|
12
|
-
GET_ORG_INFO_URL_TOKEN
|
|
13
|
-
GET_JIRA_INFO_URL_TOKEN
|
|
12
|
+
GET_ORG_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
13
|
+
GET_JIRA_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
14
14
|
|
|
15
15
|
# Authorization and logging
|
|
16
|
-
PERMISSIONS=
|
|
17
|
-
LOG_LEVEL=
|
|
18
|
-
CONSOLE_STDOUT=
|
|
19
|
-
LOG_DIR
|
|
16
|
+
PERMISSIONS=__MASKED_FOR_NPM__
|
|
17
|
+
LOG_LEVEL=__MASKED_FOR_NPM__
|
|
18
|
+
CONSOLE_STDOUT=__MASKED_FOR_NPM__
|
|
19
|
+
LOG_DIR=__MASKED_FOR_NPM__
|
|
20
20
|
|
|
21
21
|
# Runtime environment and JWT
|
|
22
|
-
FLASK_ENV=
|
|
23
|
-
JWT_SECRET_KEY
|
|
24
|
-
JWT_EXPIRATION_SECOND=
|
|
25
|
-
JWT_RENEW_SECOND=
|
|
22
|
+
FLASK_ENV=__MASKED_FOR_NPM__
|
|
23
|
+
JWT_SECRET_KEY=__MASKED_FOR_NPM__
|
|
24
|
+
JWT_EXPIRATION_SECOND=__MASKED_FOR_NPM__
|
|
25
|
+
JWT_RENEW_SECOND=__MASKED_FOR_NPM__
|
|
26
26
|
|
|
27
27
|
# LLM provider
|
|
28
|
-
LLM_URL
|
|
29
|
-
LLM_KEY
|
|
30
|
-
LLM_MODEL=
|
|
31
|
-
LLM_MAX_TOKENS=
|
|
32
|
-
LLM_TEMPERATURE=
|
|
33
|
-
LLM_TOP_P=
|
|
28
|
+
LLM_URL=__MASKED_FOR_NPM__
|
|
29
|
+
LLM_KEY=__MASKED_FOR_NPM__
|
|
30
|
+
LLM_MODEL=__MASKED_FOR_NPM__
|
|
31
|
+
LLM_MAX_TOKENS=__MASKED_FOR_NPM__
|
|
32
|
+
LLM_TEMPERATURE=__MASKED_FOR_NPM__
|
|
33
|
+
LLM_TOP_P=__MASKED_FOR_NPM__
|
|
34
34
|
|
|
35
35
|
# Chat behavior
|
|
36
|
-
SYSTEM_PROMPT
|
|
36
|
+
SYSTEM_PROMPT=__MASKED_FOR_NPM__
|
|
37
37
|
|
|
38
38
|
# Business filter
|
|
39
|
-
FILTER_ENABLED=
|
|
40
|
-
FILTER_ALLOWED_KEYWORDS
|
|
41
|
-
FILTER_REJECTION_MESSAGE
|
|
39
|
+
FILTER_ENABLED=__MASKED_FOR_NPM__
|
|
40
|
+
FILTER_ALLOWED_KEYWORDS=__MASKED_FOR_NPM__
|
|
41
|
+
FILTER_REJECTION_MESSAGE=__MASKED_FOR_NPM__
|
|
42
42
|
|
|
43
43
|
# Tool and conversation settings
|
|
44
|
-
TOOLS_MAX_ROUNDS=
|
|
45
|
-
CONVERSATION_MAX_HISTORY=
|
|
46
|
-
CONVERSATION_TTL=
|
|
44
|
+
TOOLS_MAX_ROUNDS=__MASKED_FOR_NPM__
|
|
45
|
+
CONVERSATION_MAX_HISTORY=__MASKED_FOR_NPM__
|
|
46
|
+
CONVERSATION_TTL=__MASKED_FOR_NPM__
|
|
47
47
|
|
|
48
48
|
# RAG settings
|
|
49
|
-
RAG_ENABLED=
|
|
50
|
-
RAG_KNOWLEDGE_DIR=
|
|
51
|
-
RAG_PERSIST_DIR
|
|
52
|
-
RAG_COLLECTION=
|
|
53
|
-
RAG_EMBEDDING_MODEL=
|
|
54
|
-
RAG_EMBEDDING_MAX_CHARS=
|
|
55
|
-
RAG_EMBEDDING_BATCH_SIZE=
|
|
56
|
-
RAG_TOP_K=
|
|
57
|
-
RAG_SEMANTIC_CANDIDATE_K=
|
|
58
|
-
RAG_CONTEXT_K=
|
|
59
|
-
RAG_EXACT_CONTEXT_K=
|
|
60
|
-
RAG_EXACT_PER_FILE_CONTEXT_K=
|
|
61
|
-
RAG_PER_FILE_CONTEXT_K=
|
|
62
|
-
RAG_CHUNK_SIZE=
|
|
63
|
-
RAG_REBUILD_ON_STARTUP=
|
|
49
|
+
RAG_ENABLED=__MASKED_FOR_NPM__
|
|
50
|
+
RAG_KNOWLEDGE_DIR=__MASKED_FOR_NPM__
|
|
51
|
+
RAG_PERSIST_DIR=__MASKED_FOR_NPM__
|
|
52
|
+
RAG_COLLECTION=__MASKED_FOR_NPM__
|
|
53
|
+
RAG_EMBEDDING_MODEL=__MASKED_FOR_NPM__
|
|
54
|
+
RAG_EMBEDDING_MAX_CHARS=__MASKED_FOR_NPM__
|
|
55
|
+
RAG_EMBEDDING_BATCH_SIZE=__MASKED_FOR_NPM__
|
|
56
|
+
RAG_TOP_K=__MASKED_FOR_NPM__
|
|
57
|
+
RAG_SEMANTIC_CANDIDATE_K=__MASKED_FOR_NPM__
|
|
58
|
+
RAG_CONTEXT_K=__MASKED_FOR_NPM__
|
|
59
|
+
RAG_EXACT_CONTEXT_K=__MASKED_FOR_NPM__
|
|
60
|
+
RAG_EXACT_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
61
|
+
RAG_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
62
|
+
RAG_CHUNK_SIZE=__MASKED_FOR_NPM__
|
|
63
|
+
RAG_REBUILD_ON_STARTUP=__MASKED_FOR_NPM__
|
package/app/.env.prod.sz31
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
# Flask and request authentication
|
|
2
|
-
SECRET_KEY
|
|
2
|
+
SECRET_KEY=__MASKED_FOR_NPM__
|
|
3
3
|
|
|
4
4
|
# External API endpoints
|
|
5
|
-
GET_USE_INFO_URL
|
|
6
|
-
GET_ORG_INFO_URL
|
|
7
|
-
GET_JIRA_INFO_URL
|
|
8
|
-
GET_BIGDATA_URL
|
|
9
|
-
GET_UNION_BASE_URL
|
|
5
|
+
GET_USE_INFO_URL=__MASKED_FOR_NPM__
|
|
6
|
+
GET_ORG_INFO_URL=__MASKED_FOR_NPM__
|
|
7
|
+
GET_JIRA_INFO_URL=__MASKED_FOR_NPM__
|
|
8
|
+
GET_BIGDATA_URL=__MASKED_FOR_NPM__
|
|
9
|
+
GET_UNION_BASE_URL=__MASKED_FOR_NPM__
|
|
10
10
|
|
|
11
11
|
# Legacy external API tokens
|
|
12
|
-
GET_ORG_INFO_URL_TOKEN
|
|
13
|
-
GET_JIRA_INFO_URL_TOKEN
|
|
12
|
+
GET_ORG_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
13
|
+
GET_JIRA_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
14
14
|
|
|
15
15
|
# Authorization and logging
|
|
16
|
-
PERMISSIONS=
|
|
17
|
-
LOG_LEVEL=
|
|
18
|
-
CONSOLE_STDOUT=
|
|
19
|
-
LOG_DIR
|
|
16
|
+
PERMISSIONS=__MASKED_FOR_NPM__
|
|
17
|
+
LOG_LEVEL=__MASKED_FOR_NPM__
|
|
18
|
+
CONSOLE_STDOUT=__MASKED_FOR_NPM__
|
|
19
|
+
LOG_DIR=__MASKED_FOR_NPM__
|
|
20
20
|
|
|
21
21
|
# Runtime environment and JWT
|
|
22
|
-
FLASK_ENV=
|
|
23
|
-
JWT_SECRET_KEY
|
|
24
|
-
JWT_EXPIRATION_SECOND=
|
|
25
|
-
JWT_RENEW_SECOND=
|
|
22
|
+
FLASK_ENV=__MASKED_FOR_NPM__
|
|
23
|
+
JWT_SECRET_KEY=__MASKED_FOR_NPM__
|
|
24
|
+
JWT_EXPIRATION_SECOND=__MASKED_FOR_NPM__
|
|
25
|
+
JWT_RENEW_SECOND=__MASKED_FOR_NPM__
|
|
26
26
|
|
|
27
27
|
# LLM provider
|
|
28
|
-
LLM_URL
|
|
29
|
-
LLM_KEY
|
|
30
|
-
LLM_MODEL=
|
|
31
|
-
LLM_MAX_TOKENS=
|
|
32
|
-
LLM_TEMPERATURE=
|
|
33
|
-
LLM_TOP_P=
|
|
28
|
+
LLM_URL=__MASKED_FOR_NPM__
|
|
29
|
+
LLM_KEY=__MASKED_FOR_NPM__
|
|
30
|
+
LLM_MODEL=__MASKED_FOR_NPM__
|
|
31
|
+
LLM_MAX_TOKENS=__MASKED_FOR_NPM__
|
|
32
|
+
LLM_TEMPERATURE=__MASKED_FOR_NPM__
|
|
33
|
+
LLM_TOP_P=__MASKED_FOR_NPM__
|
|
34
34
|
|
|
35
35
|
# Chat behavior
|
|
36
|
-
SYSTEM_PROMPT
|
|
36
|
+
SYSTEM_PROMPT=__MASKED_FOR_NPM__
|
|
37
37
|
|
|
38
38
|
# Business filter
|
|
39
|
-
FILTER_ENABLED=
|
|
40
|
-
FILTER_ALLOWED_KEYWORDS
|
|
41
|
-
FILTER_REJECTION_MESSAGE
|
|
39
|
+
FILTER_ENABLED=__MASKED_FOR_NPM__
|
|
40
|
+
FILTER_ALLOWED_KEYWORDS=__MASKED_FOR_NPM__
|
|
41
|
+
FILTER_REJECTION_MESSAGE=__MASKED_FOR_NPM__
|
|
42
42
|
|
|
43
43
|
# Tool and conversation settings
|
|
44
|
-
TOOLS_MAX_ROUNDS=
|
|
45
|
-
CONVERSATION_MAX_HISTORY=
|
|
46
|
-
CONVERSATION_TTL=
|
|
44
|
+
TOOLS_MAX_ROUNDS=__MASKED_FOR_NPM__
|
|
45
|
+
CONVERSATION_MAX_HISTORY=__MASKED_FOR_NPM__
|
|
46
|
+
CONVERSATION_TTL=__MASKED_FOR_NPM__
|
|
47
47
|
|
|
48
48
|
# RAG settings
|
|
49
|
-
RAG_ENABLED=
|
|
50
|
-
RAG_KNOWLEDGE_DIR=
|
|
51
|
-
RAG_PERSIST_DIR
|
|
52
|
-
RAG_COLLECTION=
|
|
53
|
-
RAG_EMBEDDING_MODEL=
|
|
54
|
-
RAG_EMBEDDING_MAX_CHARS=
|
|
55
|
-
RAG_EMBEDDING_BATCH_SIZE=
|
|
56
|
-
RAG_TOP_K=
|
|
57
|
-
RAG_SEMANTIC_CANDIDATE_K=
|
|
58
|
-
RAG_CONTEXT_K=
|
|
59
|
-
RAG_EXACT_CONTEXT_K=
|
|
60
|
-
RAG_EXACT_PER_FILE_CONTEXT_K=
|
|
61
|
-
RAG_PER_FILE_CONTEXT_K=
|
|
62
|
-
RAG_CHUNK_SIZE=
|
|
63
|
-
RAG_REBUILD_ON_STARTUP=
|
|
49
|
+
RAG_ENABLED=__MASKED_FOR_NPM__
|
|
50
|
+
RAG_KNOWLEDGE_DIR=__MASKED_FOR_NPM__
|
|
51
|
+
RAG_PERSIST_DIR=__MASKED_FOR_NPM__
|
|
52
|
+
RAG_COLLECTION=__MASKED_FOR_NPM__
|
|
53
|
+
RAG_EMBEDDING_MODEL=__MASKED_FOR_NPM__
|
|
54
|
+
RAG_EMBEDDING_MAX_CHARS=__MASKED_FOR_NPM__
|
|
55
|
+
RAG_EMBEDDING_BATCH_SIZE=__MASKED_FOR_NPM__
|
|
56
|
+
RAG_TOP_K=__MASKED_FOR_NPM__
|
|
57
|
+
RAG_SEMANTIC_CANDIDATE_K=__MASKED_FOR_NPM__
|
|
58
|
+
RAG_CONTEXT_K=__MASKED_FOR_NPM__
|
|
59
|
+
RAG_EXACT_CONTEXT_K=__MASKED_FOR_NPM__
|
|
60
|
+
RAG_EXACT_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
61
|
+
RAG_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
62
|
+
RAG_CHUNK_SIZE=__MASKED_FOR_NPM__
|
|
63
|
+
RAG_REBUILD_ON_STARTUP=__MASKED_FOR_NPM__
|
package/app/.env.test.bj12
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
1
|
# Flask and request authentication
|
|
2
|
-
SECRET_KEY
|
|
2
|
+
SECRET_KEY=__MASKED_FOR_NPM__
|
|
3
3
|
|
|
4
4
|
# External API endpoints
|
|
5
|
-
GET_USE_INFO_URL
|
|
6
|
-
GET_ORG_INFO_URL
|
|
7
|
-
GET_JIRA_INFO_URL
|
|
8
|
-
GET_BIGDATA_URL
|
|
9
|
-
GET_UNION_BASE_URL
|
|
5
|
+
GET_USE_INFO_URL=__MASKED_FOR_NPM__
|
|
6
|
+
GET_ORG_INFO_URL=__MASKED_FOR_NPM__
|
|
7
|
+
GET_JIRA_INFO_URL=__MASKED_FOR_NPM__
|
|
8
|
+
GET_BIGDATA_URL=__MASKED_FOR_NPM__
|
|
9
|
+
GET_UNION_BASE_URL=__MASKED_FOR_NPM__
|
|
10
10
|
|
|
11
11
|
# Legacy external API tokens
|
|
12
|
-
GET_ORG_INFO_URL_TOKEN
|
|
13
|
-
GET_JIRA_INFO_URL_TOKEN
|
|
12
|
+
GET_ORG_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
13
|
+
GET_JIRA_INFO_URL_TOKEN=__MASKED_FOR_NPM__
|
|
14
14
|
|
|
15
15
|
# Authorization and logging
|
|
16
|
-
PERMISSIONS=
|
|
17
|
-
LOG_LEVEL=
|
|
18
|
-
CONSOLE_STDOUT=
|
|
19
|
-
LOG_DIR
|
|
16
|
+
PERMISSIONS=__MASKED_FOR_NPM__
|
|
17
|
+
LOG_LEVEL=__MASKED_FOR_NPM__
|
|
18
|
+
CONSOLE_STDOUT=__MASKED_FOR_NPM__
|
|
19
|
+
LOG_DIR=__MASKED_FOR_NPM__
|
|
20
20
|
|
|
21
21
|
# Runtime environment and JWT
|
|
22
|
-
FLASK_ENV=
|
|
23
|
-
JWT_SECRET_KEY
|
|
24
|
-
JWT_EXPIRATION_SECOND=
|
|
25
|
-
JWT_RENEW_SECOND=
|
|
22
|
+
FLASK_ENV=__MASKED_FOR_NPM__
|
|
23
|
+
JWT_SECRET_KEY=__MASKED_FOR_NPM__
|
|
24
|
+
JWT_EXPIRATION_SECOND=__MASKED_FOR_NPM__
|
|
25
|
+
JWT_RENEW_SECOND=__MASKED_FOR_NPM__
|
|
26
26
|
|
|
27
27
|
# LLM provider
|
|
28
|
-
LLM_URL
|
|
29
|
-
LLM_KEY
|
|
30
|
-
LLM_MODEL=
|
|
31
|
-
LLM_MAX_TOKENS=
|
|
32
|
-
LLM_TEMPERATURE=
|
|
33
|
-
LLM_TOP_P=
|
|
28
|
+
LLM_URL=__MASKED_FOR_NPM__
|
|
29
|
+
LLM_KEY=__MASKED_FOR_NPM__
|
|
30
|
+
LLM_MODEL=__MASKED_FOR_NPM__
|
|
31
|
+
LLM_MAX_TOKENS=__MASKED_FOR_NPM__
|
|
32
|
+
LLM_TEMPERATURE=__MASKED_FOR_NPM__
|
|
33
|
+
LLM_TOP_P=__MASKED_FOR_NPM__
|
|
34
34
|
|
|
35
35
|
# Chat behavior
|
|
36
|
-
SYSTEM_PROMPT
|
|
36
|
+
SYSTEM_PROMPT=__MASKED_FOR_NPM__
|
|
37
37
|
|
|
38
38
|
# Business filter
|
|
39
|
-
FILTER_ENABLED=
|
|
40
|
-
FILTER_ALLOWED_KEYWORDS
|
|
41
|
-
FILTER_REJECTION_MESSAGE
|
|
39
|
+
FILTER_ENABLED=__MASKED_FOR_NPM__
|
|
40
|
+
FILTER_ALLOWED_KEYWORDS=__MASKED_FOR_NPM__
|
|
41
|
+
FILTER_REJECTION_MESSAGE=__MASKED_FOR_NPM__
|
|
42
42
|
|
|
43
43
|
# Tool and conversation settings
|
|
44
|
-
TOOLS_MAX_ROUNDS=
|
|
45
|
-
CONVERSATION_MAX_HISTORY=
|
|
46
|
-
CONVERSATION_TTL=
|
|
44
|
+
TOOLS_MAX_ROUNDS=__MASKED_FOR_NPM__
|
|
45
|
+
CONVERSATION_MAX_HISTORY=__MASKED_FOR_NPM__
|
|
46
|
+
CONVERSATION_TTL=__MASKED_FOR_NPM__
|
|
47
47
|
|
|
48
48
|
# RAG settings
|
|
49
|
-
RAG_ENABLED=
|
|
50
|
-
RAG_KNOWLEDGE_DIR=
|
|
51
|
-
RAG_PERSIST_DIR
|
|
52
|
-
RAG_COLLECTION=
|
|
53
|
-
RAG_EMBEDDING_MODEL=
|
|
54
|
-
RAG_EMBEDDING_MAX_CHARS=
|
|
55
|
-
RAG_EMBEDDING_BATCH_SIZE=
|
|
56
|
-
RAG_TOP_K=
|
|
57
|
-
RAG_SEMANTIC_CANDIDATE_K=
|
|
58
|
-
RAG_CONTEXT_K=
|
|
59
|
-
RAG_EXACT_CONTEXT_K=
|
|
60
|
-
RAG_EXACT_PER_FILE_CONTEXT_K=
|
|
61
|
-
RAG_PER_FILE_CONTEXT_K=
|
|
62
|
-
RAG_CHUNK_SIZE=
|
|
63
|
-
RAG_REBUILD_ON_STARTUP=
|
|
49
|
+
RAG_ENABLED=__MASKED_FOR_NPM__
|
|
50
|
+
RAG_KNOWLEDGE_DIR=__MASKED_FOR_NPM__
|
|
51
|
+
RAG_PERSIST_DIR=__MASKED_FOR_NPM__
|
|
52
|
+
RAG_COLLECTION=__MASKED_FOR_NPM__
|
|
53
|
+
RAG_EMBEDDING_MODEL=__MASKED_FOR_NPM__
|
|
54
|
+
RAG_EMBEDDING_MAX_CHARS=__MASKED_FOR_NPM__
|
|
55
|
+
RAG_EMBEDDING_BATCH_SIZE=__MASKED_FOR_NPM__
|
|
56
|
+
RAG_TOP_K=__MASKED_FOR_NPM__
|
|
57
|
+
RAG_SEMANTIC_CANDIDATE_K=__MASKED_FOR_NPM__
|
|
58
|
+
RAG_CONTEXT_K=__MASKED_FOR_NPM__
|
|
59
|
+
RAG_EXACT_CONTEXT_K=__MASKED_FOR_NPM__
|
|
60
|
+
RAG_EXACT_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
61
|
+
RAG_PER_FILE_CONTEXT_K=__MASKED_FOR_NPM__
|
|
62
|
+
RAG_CHUNK_SIZE=__MASKED_FOR_NPM__
|
|
63
|
+
RAG_REBUILD_ON_STARTUP=__MASKED_FOR_NPM__
|
package/app/config/env_config.py
CHANGED
|
@@ -71,6 +71,12 @@ class Config:
|
|
|
71
71
|
)
|
|
72
72
|
|
|
73
73
|
TOOLS_MAX_ROUNDS = _env_int("TOOLS_MAX_ROUNDS", 5)
|
|
74
|
+
TOOL_CALL_HEARTBEAT_INTERVAL = _env_float("TOOL_CALL_HEARTBEAT_INTERVAL", 15.0)
|
|
75
|
+
CHAT_OPENING_QUESTIONS = _env_list("CHAT_OPENING_QUESTIONS", [
|
|
76
|
+
"上周全链路运行质量如何",
|
|
77
|
+
"最近有哪些成员机构交易异常",
|
|
78
|
+
"当前系统运行风险点有哪些",
|
|
79
|
+
])
|
|
74
80
|
|
|
75
81
|
CONVERSATION_MAX_HISTORY = _env_int("CONVERSATION_MAX_HISTORY", 20)
|
|
76
82
|
CONVERSATION_TTL = _env_int("CONVERSATION_TTL", 3600)
|
|
@@ -14,6 +14,8 @@ class ChatstreamManager:
|
|
|
14
14
|
self._chat_service = chat_service
|
|
15
15
|
self._rag_service = rag_service
|
|
16
16
|
self._conversations: Dict[str, Dict] = {}
|
|
17
|
+
# ponytail: process-local guard; use shared storage only if workers need cross-process cancellation.
|
|
18
|
+
self._active_streams: Dict[str, Dict] = {}
|
|
17
19
|
self._max_history = config["CONVERSATION_MAX_HISTORY"]
|
|
18
20
|
self._ttl = config["CONVERSATION_TTL"]
|
|
19
21
|
self._lock = threading.Lock()
|
|
@@ -62,6 +64,24 @@ class ChatstreamManager:
|
|
|
62
64
|
if len(conversation["messages"]) > max_messages:
|
|
63
65
|
conversation["messages"] = conversation["messages"][-max_messages:]
|
|
64
66
|
|
|
67
|
+
def _start_stream(self, jsessionid: str, conversation_id: str) -> threading.Event:
|
|
68
|
+
abort_event = threading.Event()
|
|
69
|
+
with self._lock:
|
|
70
|
+
active = self._active_streams.get(jsessionid)
|
|
71
|
+
if active and active["conversation_id"] != conversation_id:
|
|
72
|
+
active["abort_event"].set()
|
|
73
|
+
self._active_streams[jsessionid] = {
|
|
74
|
+
"conversation_id": conversation_id,
|
|
75
|
+
"abort_event": abort_event,
|
|
76
|
+
}
|
|
77
|
+
return abort_event
|
|
78
|
+
|
|
79
|
+
def _finish_stream(self, jsessionid: str, abort_event: threading.Event):
|
|
80
|
+
with self._lock:
|
|
81
|
+
active = self._active_streams.get(jsessionid)
|
|
82
|
+
if active and active["abort_event"] is abort_event:
|
|
83
|
+
del self._active_streams[jsessionid]
|
|
84
|
+
|
|
65
85
|
def chat_stream(
|
|
66
86
|
self,
|
|
67
87
|
conversation_id: Optional[str],
|
|
@@ -69,22 +89,34 @@ class ChatstreamManager:
|
|
|
69
89
|
jsessionid: str,
|
|
70
90
|
) -> Generator[ChatResponse, None, None]:
|
|
71
91
|
normalized_conversation_id = self.normalize_conversation_id(conversation_id)
|
|
92
|
+
abort_event = self._start_stream(jsessionid, normalized_conversation_id)
|
|
72
93
|
history = self._get_history(normalized_conversation_id)
|
|
73
94
|
answer_parts: List[str] = []
|
|
74
|
-
|
|
95
|
+
saved = False
|
|
75
96
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
try:
|
|
98
|
+
for chunk in self._chat_service.tool_call_stream(
|
|
99
|
+
normalized_conversation_id,
|
|
100
|
+
question,
|
|
101
|
+
tools,
|
|
102
|
+
history,
|
|
103
|
+
jsessionid,
|
|
104
|
+
):
|
|
105
|
+
if abort_event.is_set():
|
|
106
|
+
yield ChatResponse(
|
|
107
|
+
conversationId=normalized_conversation_id,
|
|
108
|
+
content="当前对话已被新的对话替换,已停止。",
|
|
109
|
+
finish_reason="abort",
|
|
110
|
+
)
|
|
111
|
+
return
|
|
112
|
+
if chunk.content:
|
|
113
|
+
answer_parts.append(chunk.content)
|
|
114
|
+
if chunk.finish_reason and answer_parts and not saved:
|
|
115
|
+
self._append_exchange(normalized_conversation_id, question, "".join(answer_parts))
|
|
116
|
+
saved = True
|
|
117
|
+
yield chunk
|
|
88
118
|
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
if answer_parts and not saved:
|
|
120
|
+
self._append_exchange(normalized_conversation_id, question, "".join(answer_parts))
|
|
121
|
+
finally:
|
|
122
|
+
self._finish_stream(jsessionid, abort_event)
|
package/app/models/schemas.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from typing import Literal, Optional
|
|
1
|
+
from typing import Any, Dict, Literal, Optional
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, ConfigDict, Field
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ChatRequest(BaseModel):
|
|
7
7
|
"""聊天请求模型"""
|
|
8
|
-
model_config = ConfigDict(extra="
|
|
8
|
+
model_config = ConfigDict(extra="ignore")
|
|
9
9
|
|
|
10
10
|
conversation_id: Optional[str] = Field(
|
|
11
11
|
default=None,
|
|
@@ -24,7 +24,9 @@ class ChatResponse(BaseModel):
|
|
|
24
24
|
reasoning_content: Optional[str] = Field(default=None, description="模型推理内容增量")
|
|
25
25
|
tool_call: Optional[str] = Field(default=None, description="工具调用信息")
|
|
26
26
|
tool_result: Optional[str] = Field(default=None, description="工具执行结果")
|
|
27
|
-
|
|
27
|
+
heartbeat: Optional[Dict[str, Any]] = Field(default=None, description="长耗时工具调用心跳")
|
|
28
|
+
error_msg: Optional[str] = Field(default=None, alias="errorMsg", description="错误信息")
|
|
29
|
+
finish_reason: Optional[Literal["stop", "error", "rejected", "done", "abort"]] = Field(
|
|
28
30
|
default=None,
|
|
29
31
|
description="结束原因;中间流式增量为空",
|
|
30
32
|
)
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import time
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeoutError
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
1
5
|
from pathlib import Path
|
|
2
6
|
from typing import Dict, Generator, List
|
|
3
7
|
|
|
@@ -14,6 +18,33 @@ def _preview(text: str, limit: int = 300) -> str:
|
|
|
14
18
|
return str(text).replace("\n", " ")[:limit]
|
|
15
19
|
|
|
16
20
|
|
|
21
|
+
def _public_tool_result(tool_result: str) -> str:
|
|
22
|
+
try:
|
|
23
|
+
payload = json.loads(tool_result)
|
|
24
|
+
except json.JSONDecodeError:
|
|
25
|
+
payload = {}
|
|
26
|
+
|
|
27
|
+
if not isinstance(payload, dict):
|
|
28
|
+
payload = {}
|
|
29
|
+
|
|
30
|
+
public_payload = {
|
|
31
|
+
key: payload[key]
|
|
32
|
+
for key in ("tool_name", "display_name", "arguments", "message")
|
|
33
|
+
if key in payload
|
|
34
|
+
}
|
|
35
|
+
public_payload.setdefault("message", "工具调用完成")
|
|
36
|
+
return json.dumps(public_payload, ensure_ascii=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _time_context() -> str:
|
|
40
|
+
now = datetime.now(timezone.utc).astimezone(timezone(timedelta(hours=8)))
|
|
41
|
+
return (
|
|
42
|
+
"当前基准时间:"
|
|
43
|
+
f"{now:%Y-%m-%d %H:%M:%S} UTC+8。"
|
|
44
|
+
"用户问题中的“当前、近期、上周、昨天、今天”等相对时间,均按该基准时间计算。"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
17
48
|
def _load_prompts() -> Dict[str, str]:
|
|
18
49
|
path = Path(__file__).resolve().parents[2] / "tools" / "prompts.yaml"
|
|
19
50
|
if not path.exists():
|
|
@@ -55,6 +86,7 @@ class ChatService:
|
|
|
55
86
|
self._top_p = config["LLM_TOP_P"]
|
|
56
87
|
self._system_prompt = config["SYSTEM_PROMPT"]
|
|
57
88
|
self._tools_max_rounds = config["TOOLS_MAX_ROUNDS"]
|
|
89
|
+
self._tool_heartbeat_interval = float(config.get("TOOL_CALL_HEARTBEAT_INTERVAL", 15.0))
|
|
58
90
|
self._rag = rag_service
|
|
59
91
|
self._union_service = union_service
|
|
60
92
|
|
|
@@ -76,6 +108,7 @@ class ChatService:
|
|
|
76
108
|
messages = []
|
|
77
109
|
if self._system_prompt:
|
|
78
110
|
messages.append({"role": "system", "content": self._system_prompt})
|
|
111
|
+
messages.append({"role": "system", "content": _time_context()})
|
|
79
112
|
messages.extend(history)
|
|
80
113
|
messages.append({"role": "user", "content": user_question})
|
|
81
114
|
return messages
|
|
@@ -118,12 +151,32 @@ class ChatService:
|
|
|
118
151
|
return ChatResponse(conversationId=conversation_id, tool_call=tool_call)
|
|
119
152
|
|
|
120
153
|
def tool_result_event(tool_result: str) -> ChatResponse:
|
|
121
|
-
return ChatResponse(conversationId=conversation_id, tool_result=tool_result)
|
|
154
|
+
return ChatResponse(conversationId=conversation_id, tool_result=_public_tool_result(tool_result))
|
|
155
|
+
|
|
156
|
+
def heartbeat_event(tool_name: str, elapsed_seconds: float) -> ChatResponse:
|
|
157
|
+
return ChatResponse(
|
|
158
|
+
conversationId=conversation_id,
|
|
159
|
+
heartbeat={
|
|
160
|
+
"type": "tool_call_running",
|
|
161
|
+
"tool": tool_name,
|
|
162
|
+
"elapsedSeconds": round(elapsed_seconds, 3),
|
|
163
|
+
"message": f"工具 {tool_name} 仍在执行,请继续等待。",
|
|
164
|
+
},
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def error_event(error_msg: str) -> ChatResponse:
|
|
168
|
+
return ChatResponse(
|
|
169
|
+
conversationId=conversation_id,
|
|
170
|
+
content=f"[错误] {error_msg}",
|
|
171
|
+
errorMsg=error_msg,
|
|
172
|
+
finish_reason="error",
|
|
173
|
+
)
|
|
122
174
|
|
|
123
175
|
try:
|
|
124
176
|
messages = self._build_tool_messages(history, question)
|
|
125
177
|
max_rounds = self._tools_max_rounds
|
|
126
178
|
final_answer = ""
|
|
179
|
+
completed_without_tool_call = False
|
|
127
180
|
|
|
128
181
|
logger.info(f"开始模型流式调用。conversation_id={conversation_id} model={self._model} question={_preview(question, 120)}")
|
|
129
182
|
for round_idx in range(max_rounds):
|
|
@@ -168,6 +221,7 @@ class ChatService:
|
|
|
168
221
|
|
|
169
222
|
if not tool_calls_map:
|
|
170
223
|
final_answer = current_content
|
|
224
|
+
completed_without_tool_call = True
|
|
171
225
|
break
|
|
172
226
|
|
|
173
227
|
assistant_tool_calls = [tool_calls_map[i] for i in sorted(tool_calls_map)]
|
|
@@ -189,16 +243,33 @@ class ChatService:
|
|
|
189
243
|
rag_service=self._rag,
|
|
190
244
|
jsessionid=jsessionid,
|
|
191
245
|
)
|
|
192
|
-
result =
|
|
246
|
+
result = yield from self._call_function_with_heartbeats(
|
|
247
|
+
name,
|
|
248
|
+
args,
|
|
249
|
+
tool_context,
|
|
250
|
+
heartbeat_event,
|
|
251
|
+
)
|
|
193
252
|
logger.info(f"工具调用完成。conversation_id={conversation_id} tool={name} result_preview={_preview(result, 300)}")
|
|
194
253
|
yield tool_result_event(result)
|
|
195
254
|
|
|
255
|
+
tool_error = self._extract_tool_error(result)
|
|
256
|
+
if tool_error:
|
|
257
|
+
logger.error(f"工具调用失败。conversation_id={conversation_id} tool={name} error={tool_error}")
|
|
258
|
+
yield error_event(f"工具调用失败: {tool_error}")
|
|
259
|
+
return
|
|
260
|
+
|
|
196
261
|
messages.append({
|
|
197
262
|
"role": "tool",
|
|
198
263
|
"content": result,
|
|
199
264
|
"tool_call_id": tc["id"],
|
|
200
265
|
})
|
|
201
266
|
|
|
267
|
+
if not completed_without_tool_call:
|
|
268
|
+
error_msg = f"工具调用轮数达到上限({max_rounds}轮)"
|
|
269
|
+
logger.error(f"对话异常结束。conversation_id={conversation_id} error={error_msg}")
|
|
270
|
+
yield error_event(error_msg)
|
|
271
|
+
return
|
|
272
|
+
|
|
202
273
|
logger.info(f"对话完成。conversation_id={conversation_id} final_answer_chars={len(final_answer)} final_answer_preview={_preview(final_answer)}")
|
|
203
274
|
yield ChatResponse(conversationId=conversation_id, finish_reason="stop")
|
|
204
275
|
|
|
@@ -207,9 +278,52 @@ class ChatService:
|
|
|
207
278
|
yield ChatResponse(
|
|
208
279
|
conversationId=conversation_id,
|
|
209
280
|
content=f"[错误] 模型调用异常: {str(e)}",
|
|
281
|
+
errorMsg=f"模型调用异常: {str(e)}",
|
|
210
282
|
finish_reason="error",
|
|
211
283
|
)
|
|
212
284
|
|
|
285
|
+
def _call_function_with_heartbeats(
|
|
286
|
+
self,
|
|
287
|
+
name: str,
|
|
288
|
+
args: str,
|
|
289
|
+
tool_context: ToolContext,
|
|
290
|
+
heartbeat_event,
|
|
291
|
+
) -> Generator[ChatResponse, None, str]:
|
|
292
|
+
interval = self._tool_heartbeat_interval
|
|
293
|
+
if interval <= 0:
|
|
294
|
+
return call_function(name, args, tool_context)
|
|
295
|
+
|
|
296
|
+
executor = ThreadPoolExecutor(max_workers=1)
|
|
297
|
+
future = executor.submit(call_function, name, args, tool_context)
|
|
298
|
+
started_at = time.monotonic()
|
|
299
|
+
try:
|
|
300
|
+
while True:
|
|
301
|
+
try:
|
|
302
|
+
return future.result(timeout=interval)
|
|
303
|
+
except FutureTimeoutError:
|
|
304
|
+
yield heartbeat_event(name, time.monotonic() - started_at)
|
|
305
|
+
finally:
|
|
306
|
+
executor.shutdown(wait=False, cancel_futures=True)
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
def _extract_tool_error(result: str) -> str:
|
|
310
|
+
try:
|
|
311
|
+
payload = json.loads(result)
|
|
312
|
+
except json.JSONDecodeError:
|
|
313
|
+
return ""
|
|
314
|
+
if not isinstance(payload, dict):
|
|
315
|
+
return ""
|
|
316
|
+
|
|
317
|
+
error = payload.get("error")
|
|
318
|
+
if error:
|
|
319
|
+
return str(error)
|
|
320
|
+
|
|
321
|
+
status = payload.get("status")
|
|
322
|
+
if status and status != "success":
|
|
323
|
+
message = payload.get("message") or f"工具返回状态: {status}"
|
|
324
|
+
return str(message)
|
|
325
|
+
return ""
|
|
326
|
+
|
|
213
327
|
@staticmethod
|
|
214
328
|
def _merge_tool_call_delta(tool_calls_map: Dict[int, Dict], tc) -> None:
|
|
215
329
|
"""将单个流式 tool_call 增量按 index 合并到累积字典中"""
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from itertools import chain
|
|
3
|
+
|
|
1
4
|
from pydantic import ValidationError
|
|
2
5
|
from flask import Blueprint, current_app, request, Response, jsonify, stream_with_context, g
|
|
3
6
|
|
|
@@ -10,6 +13,25 @@ def _sse_event(event: str, data: str) -> str:
|
|
|
10
13
|
return f"event: {event}\ndata: {data}\n\n"
|
|
11
14
|
|
|
12
15
|
|
|
16
|
+
def _error_status(error_msg: str) -> int:
|
|
17
|
+
match = re.search(r"Error code: (\d{3})", error_msg)
|
|
18
|
+
if match:
|
|
19
|
+
status = int(match.group(1))
|
|
20
|
+
if 400 <= status <= 599:
|
|
21
|
+
return status
|
|
22
|
+
return 500
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _error_payload(chunk: ChatResponse) -> dict:
|
|
26
|
+
error_msg = chunk.error_msg or chunk.content or "聊天流异常"
|
|
27
|
+
return {
|
|
28
|
+
"conversationId": chunk.conversation_id,
|
|
29
|
+
"detail": error_msg,
|
|
30
|
+
"errorMsg": error_msg,
|
|
31
|
+
"finish_reason": "error",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
13
35
|
def _chatstream_manager():
|
|
14
36
|
return current_app.extensions["chatstream_manager"]
|
|
15
37
|
|
|
@@ -27,6 +49,13 @@ def rag_force_rebuild():
|
|
|
27
49
|
return jsonify({"detail": str(exc)}), 500
|
|
28
50
|
|
|
29
51
|
|
|
52
|
+
@chatstream.post("/chat/opening")
|
|
53
|
+
def chat_opening():
|
|
54
|
+
return jsonify({
|
|
55
|
+
"suggestedQuestions": current_app.config.get("CHAT_OPENING_QUESTIONS", [])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
|
|
30
59
|
@chatstream.route("/chat/stream", methods=["OPTIONS", "POST"])
|
|
31
60
|
def chat_stream_endpoint():
|
|
32
61
|
if request.method == "OPTIONS":
|
|
@@ -37,12 +66,40 @@ def chat_stream_endpoint():
|
|
|
37
66
|
except ValidationError as exc:
|
|
38
67
|
return jsonify({"detail": exc.errors()}), 422
|
|
39
68
|
|
|
69
|
+
conversation_id = chat_request.conversation_id
|
|
70
|
+
jsessionid = g.current_user["jsessionid"]
|
|
71
|
+
stream = _chatstream_manager().chat_stream(conversation_id, chat_request.question, jsessionid)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
first_chunk = next(stream)
|
|
75
|
+
except StopIteration:
|
|
76
|
+
first_chunk = ChatResponse(conversationId=conversation_id, finish_reason="done")
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
error_msg = f"聊天流异常: {exc}"
|
|
79
|
+
return jsonify({"detail": error_msg, "errorMsg": error_msg, "finish_reason": "error"}), 500
|
|
80
|
+
|
|
81
|
+
if first_chunk.finish_reason == "error":
|
|
82
|
+
return jsonify(_error_payload(first_chunk)), _error_status(first_chunk.error_msg or first_chunk.content or "")
|
|
83
|
+
|
|
40
84
|
def event_generator():
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
85
|
+
nonlocal conversation_id
|
|
86
|
+
try:
|
|
87
|
+
for chunk in chain([first_chunk], stream):
|
|
88
|
+
conversation_id = chunk.conversation_id
|
|
89
|
+
event = "heartbeat" if chunk.heartbeat else "message"
|
|
90
|
+
yield _sse_event(event, chunk.model_dump_json(by_alias=True))
|
|
91
|
+
if chunk.finish_reason == "error":
|
|
92
|
+
return
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
error_msg = f"聊天流异常: {exc}"
|
|
95
|
+
error = ChatResponse(
|
|
96
|
+
conversationId=conversation_id,
|
|
97
|
+
content=f"[错误] {error_msg}",
|
|
98
|
+
errorMsg=error_msg,
|
|
99
|
+
finish_reason="error",
|
|
100
|
+
)
|
|
101
|
+
yield _sse_event("message", error.model_dump_json(by_alias=True))
|
|
102
|
+
return
|
|
46
103
|
|
|
47
104
|
done = ChatResponse(conversationId=conversation_id, finish_reason="done")
|
|
48
105
|
yield _sse_event("done", done.model_dump_json(by_alias=True))
|
package/app/wsgi.py
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "union-app-chat-stream",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Union operations chat stream Flask application package.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"files": [
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
"deploy",
|
|
9
9
|
"!deploy/offline-packages",
|
|
10
10
|
"!deploy/offline-packages/**",
|
|
11
|
+
"!app/**/__pycache__",
|
|
12
|
+
"!app/**/*.pyc",
|
|
11
13
|
"knowledge",
|
|
12
14
|
"tools",
|
|
13
15
|
"PROJECT_OVERVIEW.md",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|