Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>tags/1.7.0
| import logging | import logging | ||||
| from flask import request | |||||
| from flask_restful import Resource, reqparse | from flask_restful import Resource, reqparse | ||||
| from werkzeug.exceptions import InternalServerError, NotFound | from werkzeug.exceptions import InternalServerError, NotFound | ||||
| ProviderTokenNotInitError, | ProviderTokenNotInitError, | ||||
| QuotaExceededError, | QuotaExceededError, | ||||
| ) | ) | ||||
| from core.helper.trace_id_helper import get_external_trace_id | |||||
| from core.model_runtime.errors.invoke import InvokeError | from core.model_runtime.errors.invoke import InvokeError | ||||
| from libs import helper | from libs import helper | ||||
| from libs.helper import uuid_value | from libs.helper import uuid_value | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| external_trace_id = get_external_trace_id(request) | |||||
| if external_trace_id: | |||||
| args["external_trace_id"] = external_trace_id | |||||
| streaming = args["response_mode"] == "streaming" | streaming = args["response_mode"] == "streaming" | ||||
| try: | try: |
| import logging | import logging | ||||
| from dateutil.parser import isoparse | from dateutil.parser import isoparse | ||||
| from flask import request | |||||
| from flask_restful import Resource, fields, marshal_with, reqparse | from flask_restful import Resource, fields, marshal_with, reqparse | ||||
| from flask_restful.inputs import int_range | from flask_restful.inputs import int_range | ||||
| from sqlalchemy.orm import Session, sessionmaker | from sqlalchemy.orm import Session, sessionmaker | ||||
| ProviderTokenNotInitError, | ProviderTokenNotInitError, | ||||
| QuotaExceededError, | QuotaExceededError, | ||||
| ) | ) | ||||
| from core.helper.trace_id_helper import get_external_trace_id | |||||
| from core.model_runtime.errors.invoke import InvokeError | from core.model_runtime.errors.invoke import InvokeError | ||||
| from core.workflow.entities.workflow_execution import WorkflowExecutionStatus | from core.workflow.entities.workflow_execution import WorkflowExecutionStatus | ||||
| from extensions.ext_database import db | from extensions.ext_database import db | ||||
| parser.add_argument("files", type=list, required=False, location="json") | parser.add_argument("files", type=list, required=False, location="json") | ||||
| parser.add_argument("response_mode", type=str, choices=["blocking", "streaming"], location="json") | parser.add_argument("response_mode", type=str, choices=["blocking", "streaming"], location="json") | ||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| external_trace_id = get_external_trace_id(request) | |||||
| if external_trace_id: | |||||
| args["external_trace_id"] = external_trace_id | |||||
| streaming = args.get("response_mode") == "streaming" | streaming = args.get("response_mode") == "streaming" | ||||
| try: | try: |
| from core.app.apps.message_based_app_queue_manager import MessageBasedAppQueueManager | from core.app.apps.message_based_app_queue_manager import MessageBasedAppQueueManager | ||||
| from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom | from core.app.entities.app_invoke_entities import AdvancedChatAppGenerateEntity, InvokeFrom | ||||
| from core.app.entities.task_entities import ChatbotAppBlockingResponse, ChatbotAppStreamResponse | from core.app.entities.task_entities import ChatbotAppBlockingResponse, ChatbotAppStreamResponse | ||||
| from core.helper.trace_id_helper import extract_external_trace_id_from_args | |||||
| from core.model_runtime.errors.invoke import InvokeAuthorizationError | from core.model_runtime.errors.invoke import InvokeAuthorizationError | ||||
| from core.ops.ops_trace_manager import TraceQueueManager | from core.ops.ops_trace_manager import TraceQueueManager | ||||
| from core.prompt.utils.get_thread_messages_length import get_thread_messages_length | from core.prompt.utils.get_thread_messages_length import get_thread_messages_length | ||||
| query = query.replace("\x00", "") | query = query.replace("\x00", "") | ||||
| inputs = args["inputs"] | inputs = args["inputs"] | ||||
| extras = {"auto_generate_conversation_name": args.get("auto_generate_name", False)} | |||||
| extras = { | |||||
| "auto_generate_conversation_name": args.get("auto_generate_name", False), | |||||
| **extract_external_trace_id_from_args(args), | |||||
| } | |||||
| # get conversation | # get conversation | ||||
| conversation = None | conversation = None |
| outputs=event.outputs, | outputs=event.outputs, | ||||
| conversation_id=self._conversation_id, | conversation_id=self._conversation_id, | ||||
| trace_manager=trace_manager, | trace_manager=trace_manager, | ||||
| external_trace_id=self._application_generate_entity.extras.get("external_trace_id"), | |||||
| ) | ) | ||||
| workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( | workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( | ||||
| session=session, | session=session, | ||||
| exceptions_count=event.exceptions_count, | exceptions_count=event.exceptions_count, | ||||
| conversation_id=None, | conversation_id=None, | ||||
| trace_manager=trace_manager, | trace_manager=trace_manager, | ||||
| external_trace_id=self._application_generate_entity.extras.get("external_trace_id"), | |||||
| ) | ) | ||||
| workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( | workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( | ||||
| session=session, | session=session, | ||||
| conversation_id=self._conversation_id, | conversation_id=self._conversation_id, | ||||
| trace_manager=trace_manager, | trace_manager=trace_manager, | ||||
| exceptions_count=event.exceptions_count, | exceptions_count=event.exceptions_count, | ||||
| external_trace_id=self._application_generate_entity.extras.get("external_trace_id"), | |||||
| ) | ) | ||||
| workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( | workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( | ||||
| session=session, | session=session, | ||||
| error_message=event.get_stop_reason(), | error_message=event.get_stop_reason(), | ||||
| conversation_id=self._conversation_id, | conversation_id=self._conversation_id, | ||||
| trace_manager=trace_manager, | trace_manager=trace_manager, | ||||
| external_trace_id=self._application_generate_entity.extras.get("external_trace_id"), | |||||
| ) | ) | ||||
| workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( | workflow_finish_resp = self._workflow_response_converter.workflow_finish_to_stream_response( | ||||
| session=session, | session=session, |
| from core.app.apps.workflow.generate_task_pipeline import WorkflowAppGenerateTaskPipeline | from core.app.apps.workflow.generate_task_pipeline import WorkflowAppGenerateTaskPipeline | ||||
| from core.app.entities.app_invoke_entities import InvokeFrom, WorkflowAppGenerateEntity | from core.app.entities.app_invoke_entities import InvokeFrom, WorkflowAppGenerateEntity | ||||
| from core.app.entities.task_entities import WorkflowAppBlockingResponse, WorkflowAppStreamResponse | from core.app.entities.task_entities import WorkflowAppBlockingResponse, WorkflowAppStreamResponse | ||||
| from core.helper.trace_id_helper import extract_external_trace_id_from_args | |||||
| from core.model_runtime.errors.invoke import InvokeAuthorizationError | from core.model_runtime.errors.invoke import InvokeAuthorizationError | ||||
| from core.ops.ops_trace_manager import TraceQueueManager | from core.ops.ops_trace_manager import TraceQueueManager | ||||
| from core.repositories import DifyCoreRepositoryFactory | from core.repositories import DifyCoreRepositoryFactory | ||||
| ) | ) | ||||
| inputs: Mapping[str, Any] = args["inputs"] | inputs: Mapping[str, Any] = args["inputs"] | ||||
| extras = { | |||||
| **extract_external_trace_id_from_args(args), | |||||
| } | |||||
| workflow_run_id = str(uuid.uuid4()) | workflow_run_id = str(uuid.uuid4()) | ||||
| # init application generate entity | # init application generate entity | ||||
| application_generate_entity = WorkflowAppGenerateEntity( | application_generate_entity = WorkflowAppGenerateEntity( | ||||
| call_depth=call_depth, | call_depth=call_depth, | ||||
| trace_manager=trace_manager, | trace_manager=trace_manager, | ||||
| workflow_execution_id=workflow_run_id, | workflow_execution_id=workflow_run_id, | ||||
| extras=extras, | |||||
| ) | ) | ||||
| contexts.plugin_tool_providers.set({}) | contexts.plugin_tool_providers.set({}) |
| outputs=event.outputs, | outputs=event.outputs, | ||||
| conversation_id=None, | conversation_id=None, | ||||
| trace_manager=trace_manager, | trace_manager=trace_manager, | ||||
| external_trace_id=self._application_generate_entity.extras.get("external_trace_id"), | |||||
| ) | ) | ||||
| # save workflow app log | # save workflow app log | ||||
| exceptions_count=event.exceptions_count, | exceptions_count=event.exceptions_count, | ||||
| conversation_id=None, | conversation_id=None, | ||||
| trace_manager=trace_manager, | trace_manager=trace_manager, | ||||
| external_trace_id=self._application_generate_entity.extras.get("external_trace_id"), | |||||
| ) | ) | ||||
| # save workflow app log | # save workflow app log | ||||
| conversation_id=None, | conversation_id=None, | ||||
| trace_manager=trace_manager, | trace_manager=trace_manager, | ||||
| exceptions_count=event.exceptions_count if isinstance(event, QueueWorkflowFailedEvent) else 0, | exceptions_count=event.exceptions_count if isinstance(event, QueueWorkflowFailedEvent) else 0, | ||||
| external_trace_id=self._application_generate_entity.extras.get("external_trace_id"), | |||||
| ) | ) | ||||
| # save workflow app log | # save workflow app log |
| import re | |||||
| from collections.abc import Mapping | |||||
| from typing import Any, Optional | |||||
| def is_valid_trace_id(trace_id: str) -> bool: | |||||
| """ | |||||
| Check if the trace_id is valid. | |||||
| Requirements: 1-128 characters, only letters, numbers, '-', and '_'. | |||||
| """ | |||||
| return bool(re.match(r"^[a-zA-Z0-9\-_]{1,128}$", trace_id)) | |||||
| def get_external_trace_id(request: Any) -> Optional[str]: | |||||
| """ | |||||
| Retrieve the trace_id from the request. | |||||
| Priority: header ('X-Trace-Id'), then parameters, then JSON body. Returns None if not provided or invalid. | |||||
| """ | |||||
| trace_id = request.headers.get("X-Trace-Id") | |||||
| if not trace_id: | |||||
| trace_id = request.args.get("trace_id") | |||||
| if not trace_id and getattr(request, "is_json", False): | |||||
| json_data = getattr(request, "json", None) | |||||
| if json_data: | |||||
| trace_id = json_data.get("trace_id") | |||||
| if isinstance(trace_id, str) and is_valid_trace_id(trace_id): | |||||
| return trace_id | |||||
| return None | |||||
| def extract_external_trace_id_from_args(args: Mapping[str, Any]) -> dict: | |||||
| """ | |||||
| Extract 'external_trace_id' from args. | |||||
| Returns a dict suitable for use in extras. Returns an empty dict if not found. | |||||
| """ | |||||
| trace_id = args.get("external_trace_id") | |||||
| if trace_id: | |||||
| return {"external_trace_id": trace_id} | |||||
| return {} |
| raise ValueError(f"Aliyun get run url failed: {str(e)}") | raise ValueError(f"Aliyun get run url failed: {str(e)}") | ||||
| def workflow_trace(self, trace_info: WorkflowTraceInfo): | def workflow_trace(self, trace_info: WorkflowTraceInfo): | ||||
| trace_id = convert_to_trace_id(trace_info.workflow_run_id) | |||||
| external_trace_id = trace_info.metadata.get("external_trace_id") | |||||
| trace_id = external_trace_id or convert_to_trace_id(trace_info.workflow_run_id) | |||||
| workflow_span_id = convert_to_span_id(trace_info.workflow_run_id, "workflow") | workflow_span_id = convert_to_span_id(trace_info.workflow_run_id, "workflow") | ||||
| self.add_workflow_span(trace_id, workflow_span_id, trace_info) | self.add_workflow_span(trace_id, workflow_span_id, trace_info) | ||||
| } | } | ||||
| workflow_metadata.update(trace_info.metadata) | workflow_metadata.update(trace_info.metadata) | ||||
| trace_id = uuid_to_trace_id(trace_info.workflow_run_id) | |||||
| external_trace_id = trace_info.metadata.get("external_trace_id") | |||||
| trace_id = external_trace_id or uuid_to_trace_id(trace_info.workflow_run_id) | |||||
| span_id = RandomIdGenerator().generate_span_id() | span_id = RandomIdGenerator().generate_span_id() | ||||
| context = SpanContext( | context = SpanContext( | ||||
| trace_id=trace_id, | trace_id=trace_id, |
| self.generate_name_trace(trace_info) | self.generate_name_trace(trace_info) | ||||
| def workflow_trace(self, trace_info: WorkflowTraceInfo): | def workflow_trace(self, trace_info: WorkflowTraceInfo): | ||||
| trace_id = trace_info.workflow_run_id | |||||
| external_trace_id = trace_info.metadata.get("external_trace_id") | |||||
| trace_id = external_trace_id or trace_info.workflow_run_id | |||||
| user_id = trace_info.metadata.get("user_id") | user_id = trace_info.metadata.get("user_id") | ||||
| metadata = trace_info.metadata | metadata = trace_info.metadata | ||||
| metadata["workflow_app_log_id"] = trace_info.workflow_app_log_id | metadata["workflow_app_log_id"] = trace_info.workflow_app_log_id | ||||
| if trace_info.message_id: | if trace_info.message_id: | ||||
| trace_id = trace_info.message_id | |||||
| trace_id = external_trace_id or trace_info.message_id | |||||
| name = TraceTaskName.MESSAGE_TRACE.value | name = TraceTaskName.MESSAGE_TRACE.value | ||||
| trace_data = LangfuseTrace( | trace_data = LangfuseTrace( | ||||
| id=trace_id, | id=trace_id, |
| self.generate_name_trace(trace_info) | self.generate_name_trace(trace_info) | ||||
| def workflow_trace(self, trace_info: WorkflowTraceInfo): | def workflow_trace(self, trace_info: WorkflowTraceInfo): | ||||
| trace_id = trace_info.message_id or trace_info.workflow_run_id | |||||
| external_trace_id = trace_info.metadata.get("external_trace_id") | |||||
| trace_id = external_trace_id or trace_info.message_id or trace_info.workflow_run_id | |||||
| if trace_info.start_time is None: | if trace_info.start_time is None: | ||||
| trace_info.start_time = datetime.now() | trace_info.start_time = datetime.now() | ||||
| message_dotted_order = ( | message_dotted_order = ( |
| self.generate_name_trace(trace_info) | self.generate_name_trace(trace_info) | ||||
| def workflow_trace(self, trace_info: WorkflowTraceInfo): | def workflow_trace(self, trace_info: WorkflowTraceInfo): | ||||
| dify_trace_id = trace_info.workflow_run_id | |||||
| external_trace_id = trace_info.metadata.get("external_trace_id") | |||||
| dify_trace_id = external_trace_id or trace_info.workflow_run_id | |||||
| opik_trace_id = prepare_opik_uuid(trace_info.start_time, dify_trace_id) | opik_trace_id = prepare_opik_uuid(trace_info.start_time, dify_trace_id) | ||||
| workflow_metadata = wrap_metadata( | workflow_metadata = wrap_metadata( | ||||
| trace_info.metadata, message_id=trace_info.message_id, workflow_app_log_id=trace_info.workflow_app_log_id | trace_info.metadata, message_id=trace_info.message_id, workflow_app_log_id=trace_info.workflow_app_log_id | ||||
| root_span_id = None | root_span_id = None | ||||
| if trace_info.message_id: | if trace_info.message_id: | ||||
| dify_trace_id = trace_info.message_id | |||||
| dify_trace_id = external_trace_id or trace_info.message_id | |||||
| opik_trace_id = prepare_opik_uuid(trace_info.start_time, dify_trace_id) | opik_trace_id = prepare_opik_uuid(trace_info.start_time, dify_trace_id) | ||||
| trace_data = { | trace_data = { |
| "app_id": workflow_run.app_id, | "app_id": workflow_run.app_id, | ||||
| } | } | ||||
| external_trace_id = self.kwargs.get("external_trace_id") | |||||
| if external_trace_id: | |||||
| metadata["external_trace_id"] = external_trace_id | |||||
| workflow_trace_info = WorkflowTraceInfo( | workflow_trace_info = WorkflowTraceInfo( | ||||
| workflow_data=workflow_run.to_dict(), | workflow_data=workflow_run.to_dict(), | ||||
| conversation_id=conversation_id, | conversation_id=conversation_id, |
| self.generate_name_trace(trace_info) | self.generate_name_trace(trace_info) | ||||
| def workflow_trace(self, trace_info: WorkflowTraceInfo): | def workflow_trace(self, trace_info: WorkflowTraceInfo): | ||||
| trace_id = trace_info.message_id or trace_info.workflow_run_id | |||||
| external_trace_id = trace_info.metadata.get("external_trace_id") | |||||
| trace_id = external_trace_id or trace_info.message_id or trace_info.workflow_run_id | |||||
| if trace_info.start_time is None: | if trace_info.start_time is None: | ||||
| trace_info.start_time = datetime.now() | trace_info.start_time = datetime.now() | ||||
| outputs: Mapping[str, Any] | None = None, | outputs: Mapping[str, Any] | None = None, | ||||
| conversation_id: Optional[str] = None, | conversation_id: Optional[str] = None, | ||||
| trace_manager: Optional[TraceQueueManager] = None, | trace_manager: Optional[TraceQueueManager] = None, | ||||
| external_trace_id: Optional[str] = None, | |||||
| ) -> WorkflowExecution: | ) -> WorkflowExecution: | ||||
| workflow_execution = self._get_workflow_execution_or_raise_error(workflow_run_id) | workflow_execution = self._get_workflow_execution_or_raise_error(workflow_run_id) | ||||
| total_steps=total_steps, | total_steps=total_steps, | ||||
| ) | ) | ||||
| self._add_trace_task_if_needed(trace_manager, workflow_execution, conversation_id) | |||||
| self._add_trace_task_if_needed(trace_manager, workflow_execution, conversation_id, external_trace_id) | |||||
| self._workflow_execution_repository.save(workflow_execution) | self._workflow_execution_repository.save(workflow_execution) | ||||
| return workflow_execution | return workflow_execution | ||||
| exceptions_count: int = 0, | exceptions_count: int = 0, | ||||
| conversation_id: Optional[str] = None, | conversation_id: Optional[str] = None, | ||||
| trace_manager: Optional[TraceQueueManager] = None, | trace_manager: Optional[TraceQueueManager] = None, | ||||
| external_trace_id: Optional[str] = None, | |||||
| ) -> WorkflowExecution: | ) -> WorkflowExecution: | ||||
| execution = self._get_workflow_execution_or_raise_error(workflow_run_id) | execution = self._get_workflow_execution_or_raise_error(workflow_run_id) | ||||
| exceptions_count=exceptions_count, | exceptions_count=exceptions_count, | ||||
| ) | ) | ||||
| self._add_trace_task_if_needed(trace_manager, execution, conversation_id) | |||||
| self._add_trace_task_if_needed(trace_manager, execution, conversation_id, external_trace_id) | |||||
| self._workflow_execution_repository.save(execution) | self._workflow_execution_repository.save(execution) | ||||
| return execution | return execution | ||||
| conversation_id: Optional[str] = None, | conversation_id: Optional[str] = None, | ||||
| trace_manager: Optional[TraceQueueManager] = None, | trace_manager: Optional[TraceQueueManager] = None, | ||||
| exceptions_count: int = 0, | exceptions_count: int = 0, | ||||
| external_trace_id: Optional[str] = None, | |||||
| ) -> WorkflowExecution: | ) -> WorkflowExecution: | ||||
| workflow_execution = self._get_workflow_execution_or_raise_error(workflow_run_id) | workflow_execution = self._get_workflow_execution_or_raise_error(workflow_run_id) | ||||
| now = naive_utc_now() | now = naive_utc_now() | ||||
| ) | ) | ||||
| self._fail_running_node_executions(workflow_execution.id_, error_message, now) | self._fail_running_node_executions(workflow_execution.id_, error_message, now) | ||||
| self._add_trace_task_if_needed(trace_manager, workflow_execution, conversation_id) | |||||
| self._add_trace_task_if_needed(trace_manager, workflow_execution, conversation_id, external_trace_id) | |||||
| self._workflow_execution_repository.save(workflow_execution) | self._workflow_execution_repository.save(workflow_execution) | ||||
| return workflow_execution | return workflow_execution | ||||
| trace_manager: Optional[TraceQueueManager], | trace_manager: Optional[TraceQueueManager], | ||||
| workflow_execution: WorkflowExecution, | workflow_execution: WorkflowExecution, | ||||
| conversation_id: Optional[str], | conversation_id: Optional[str], | ||||
| external_trace_id: Optional[str], | |||||
| ) -> None: | ) -> None: | ||||
| """Add trace task if trace manager is provided.""" | """Add trace task if trace manager is provided.""" | ||||
| if trace_manager: | if trace_manager: | ||||
| workflow_execution=workflow_execution, | workflow_execution=workflow_execution, | ||||
| conversation_id=conversation_id, | conversation_id=conversation_id, | ||||
| user_id=trace_manager.user_id, | user_id=trace_manager.user_id, | ||||
| external_trace_id=external_trace_id, | |||||
| ) | ) | ||||
| ) | ) | ||||
| import pytest | |||||
| from core.helper.trace_id_helper import extract_external_trace_id_from_args, get_external_trace_id, is_valid_trace_id | |||||
| class DummyRequest: | |||||
| def __init__(self, headers=None, args=None, json=None, is_json=False): | |||||
| self.headers = headers or {} | |||||
| self.args = args or {} | |||||
| self.json = json | |||||
| self.is_json = is_json | |||||
| class TestTraceIdHelper: | |||||
| """Test cases for trace_id_helper.py""" | |||||
| @pytest.mark.parametrize( | |||||
| ("trace_id", "expected"), | |||||
| [ | |||||
| ("abc123", True), | |||||
| ("A-B_C-123", True), | |||||
| ("a" * 128, True), | |||||
| ("", False), | |||||
| ("a" * 129, False), | |||||
| ("abc!@#", False), | |||||
| ("空格", False), | |||||
| ("with space", False), | |||||
| ], | |||||
| ) | |||||
| def test_is_valid_trace_id(self, trace_id, expected): | |||||
| """Test trace_id validation for various cases""" | |||||
| assert is_valid_trace_id(trace_id) is expected | |||||
| def test_get_external_trace_id_from_header(self): | |||||
| """Should extract valid trace_id from header""" | |||||
| req = DummyRequest(headers={"X-Trace-Id": "abc123"}) | |||||
| assert get_external_trace_id(req) == "abc123" | |||||
| def test_get_external_trace_id_from_args(self): | |||||
| """Should extract valid trace_id from args if header missing""" | |||||
| req = DummyRequest(args={"trace_id": "abc123"}) | |||||
| assert get_external_trace_id(req) == "abc123" | |||||
| def test_get_external_trace_id_from_json(self): | |||||
| """Should extract valid trace_id from JSON body if header and args missing""" | |||||
| req = DummyRequest(is_json=True, json={"trace_id": "abc123"}) | |||||
| assert get_external_trace_id(req) == "abc123" | |||||
| def test_get_external_trace_id_priority(self): | |||||
| """Header > args > json priority""" | |||||
| req = DummyRequest( | |||||
| headers={"X-Trace-Id": "header_id"}, | |||||
| args={"trace_id": "args_id"}, | |||||
| is_json=True, | |||||
| json={"trace_id": "json_id"}, | |||||
| ) | |||||
| assert get_external_trace_id(req) == "header_id" | |||||
| req2 = DummyRequest(args={"trace_id": "args_id"}, is_json=True, json={"trace_id": "json_id"}) | |||||
| assert get_external_trace_id(req2) == "args_id" | |||||
| req3 = DummyRequest(is_json=True, json={"trace_id": "json_id"}) | |||||
| assert get_external_trace_id(req3) == "json_id" | |||||
| @pytest.mark.parametrize( | |||||
| "req", | |||||
| [ | |||||
| DummyRequest(headers={"X-Trace-Id": "!!!"}), | |||||
| DummyRequest(args={"trace_id": "!!!"}), | |||||
| DummyRequest(is_json=True, json={"trace_id": "!!!"}), | |||||
| DummyRequest(), | |||||
| ], | |||||
| ) | |||||
| def test_get_external_trace_id_invalid(self, req): | |||||
| """Should return None for invalid or missing trace_id""" | |||||
| assert get_external_trace_id(req) is None | |||||
| @pytest.mark.parametrize( | |||||
| ("args", "expected"), | |||||
| [ | |||||
| ({"external_trace_id": "abc123"}, {"external_trace_id": "abc123"}), | |||||
| ({"other": "value"}, {}), | |||||
| ({}, {}), | |||||
| ], | |||||
| ) | |||||
| def test_extract_external_trace_id_from_args(self, args, expected): | |||||
| """Test extraction of external_trace_id from args mapping""" | |||||
| assert extract_external_trace_id_from_args(args) == expected |
| Auto-generate title, default is `true`. | Auto-generate title, default is `true`. | ||||
| If set to `false`, can achieve async title generation by calling the conversation rename API and setting `auto_generate` to `true`. | If set to `false`, can achieve async title generation by calling the conversation rename API and setting `auto_generate` to `true`. | ||||
| </Property> | </Property> | ||||
| <Property name='trace_id' type='string' key='trace_id'> | |||||
| (Optional) Trace ID. Used for integration with existing business trace components to achieve end-to-end distributed tracing. If not provided, the system will automatically generate a trace_id. Supports the following three ways to pass, in order of priority:<br/> | |||||
| - Header: via HTTP Header <code>X-Trace-Id</code>, highest priority.<br/> | |||||
| - Query parameter: via URL query parameter <code>trace_id</code>.<br/> | |||||
| - Request Body: via request body field <code>trace_id</code> (i.e., this field).<br/> | |||||
| </Property> | |||||
| </Properties> | </Properties> | ||||
| ### Response | ### Response |
| タイトルを自動生成、デフォルトは`true`。 | タイトルを自動生成、デフォルトは`true`。 | ||||
| `false`に設定すると、会話のリネームAPIを呼び出し、`auto_generate`を`true`に設定することで非同期タイトル生成を実現できます。 | `false`に設定すると、会話のリネームAPIを呼び出し、`auto_generate`を`true`に設定することで非同期タイトル生成を実現できます。 | ||||
| </Property> | </Property> | ||||
| <Property name='trace_id' type='string' key='trace_id'> | |||||
| (オプション)トレースID。既存の業務システムのトレースコンポーネントと連携し、エンドツーエンドの分散トレーシングを実現するために使用します。指定がない場合、システムが自動的に trace_id を生成します。以下の3つの方法で渡すことができ、優先順位は次のとおりです:<br/> | |||||
| - Header:HTTPヘッダー <code>X-Trace-Id</code> で渡す(最優先)。<br/> | |||||
| - クエリパラメータ:URLクエリパラメータ <code>trace_id</code> で渡す。<br/> | |||||
| - リクエストボディ:リクエストボディの <code>trace_id</code> フィールドで渡す(本フィールド)。<br/> | |||||
| </Property> | |||||
| </Properties> | </Properties> | ||||
| ### 応答 | ### 応答 |
| <Property name='auto_generate_name' type='bool' key='auto_generate_name'> | <Property name='auto_generate_name' type='bool' key='auto_generate_name'> | ||||
| (选填)自动生成标题,默认 `true`。 若设置为 `false`,则可通过调用会话重命名接口并设置 `auto_generate` 为 `true` 实现异步生成标题。 | (选填)自动生成标题,默认 `true`。 若设置为 `false`,则可通过调用会话重命名接口并设置 `auto_generate` 为 `true` 实现异步生成标题。 | ||||
| </Property> | </Property> | ||||
| <Property name='trace_id' type='string' key='trace_id'> | |||||
| (选填)链路追踪ID。适用于与业务系统已有的trace组件打通,实现端到端分布式追踪等场景。如果未指定,系统会自动生成<code>trace_id</code>。支持以下三种方式传递,具体优先级依次为:<br/> | |||||
| - Header:通过 HTTP Header <code>X-Trace-Id</code> 传递,优先级最高。<br/> | |||||
| - Query 参数:通过 URL 查询参数 <code>trace_id</code> 传递。<br/> | |||||
| - Request Body:通过请求体字段 <code>trace_id</code> 传递(即本字段)。<br/> | |||||
| </Property> | |||||
| </Properties> | </Properties> | ||||
| ### Response | ### Response |
| Auto-generate title, default is `true`. | Auto-generate title, default is `true`. | ||||
| If set to `false`, can achieve async title generation by calling the conversation rename API and setting `auto_generate` to `true`. | If set to `false`, can achieve async title generation by calling the conversation rename API and setting `auto_generate` to `true`. | ||||
| </Property> | </Property> | ||||
| <Property name='trace_id' type='string' key='trace_id'> | |||||
| (Optional) Trace ID. Used for integration with existing business trace components to achieve end-to-end distributed tracing. If not provided, the system will automatically generate a trace_id. Supports the following three ways to pass, in order of priority:<br/> | |||||
| - Header: via HTTP Header <code>X-Trace-Id</code>, highest priority.<br/> | |||||
| - Query parameter: via URL query parameter <code>trace_id</code>.<br/> | |||||
| - Request Body: via request body field <code>trace_id</code> (i.e., this field).<br/> | |||||
| </Property> | |||||
| </Properties> | </Properties> | ||||
| ### Response | ### Response |
| タイトルを自動生成します。デフォルトは`true`です。 | タイトルを自動生成します。デフォルトは`true`です。 | ||||
| `false`に設定すると、会話のリネームAPIを呼び出し、`auto_generate`を`true`に設定することで非同期タイトル生成を実現できます。 | `false`に設定すると、会話のリネームAPIを呼び出し、`auto_generate`を`true`に設定することで非同期タイトル生成を実現できます。 | ||||
| </Property> | </Property> | ||||
| <Property name='trace_id' type='string' key='trace_id'> | |||||
| (オプション)トレースID。既存の業務システムのトレースコンポーネントと連携し、エンドツーエンドの分散トレーシングを実現するために使用します。指定がない場合、システムが自動的に trace_id を生成します。以下の3つの方法で渡すことができ、優先順位は次のとおりです:<br/> | |||||
| - Header:HTTPヘッダー <code>X-Trace-Id</code> で渡す(最優先)。<br/> | |||||
| - クエリパラメータ:URLクエリパラメータ <code>trace_id</code> で渡す。<br/> | |||||
| - リクエストボディ:リクエストボディの <code>trace_id</code> フィールドで渡す(本フィールド)。<br/> | |||||
| </Property> | |||||
| </Properties> | </Properties> | ||||
| ### 応答 | ### 応答 |
| <Property name='auto_generate_name' type='bool' key='auto_generate_name'> | <Property name='auto_generate_name' type='bool' key='auto_generate_name'> | ||||
| (选填)自动生成标题,默认 `true`。 若设置为 `false`,则可通过调用会话重命名接口并设置 `auto_generate` 为 `true` 实现异步生成标题。 | (选填)自动生成标题,默认 `true`。 若设置为 `false`,则可通过调用会话重命名接口并设置 `auto_generate` 为 `true` 实现异步生成标题。 | ||||
| </Property> | </Property> | ||||
| <Property name='trace_id' type='string' key='trace_id'> | |||||
| (选填)链路追踪ID。适用于与业务系统已有的trace组件打通,实现端到端分布式追踪等场景。如果未指定,系统会自动生成<code>trace_id</code>。支持以下三种方式传递,具体优先级依次为:<br/> | |||||
| - Header:通过 HTTP Header <code>X-Trace-Id</code> 传递,优先级最高。<br/> | |||||
| - Query 参数:通过 URL 查询参数 <code>trace_id</code> 传递。<br/> | |||||
| - Request Body:通过请求体字段 <code>trace_id</code> 传递(即本字段)。<br/> | |||||
| </Property> | |||||
| </Properties> | </Properties> | ||||
| ### Response | ### Response |
| Should be uniquely defined by the developer within the application. | Should be uniquely defined by the developer within the application. | ||||
| <br/> | <br/> | ||||
| <i>The user identifier should be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp.</i> | <i>The user identifier should be consistent with the user passed in the message sending interface. The Service API does not share conversations created by the WebApp.</i> | ||||
| - `files` (array[object]) Optional | |||||
| - `trace_id` (string) Optional | |||||
| Trace ID. Used for integration with existing business trace components to achieve end-to-end distributed tracing. If not provided, the system will automatically generate a trace_id. Supports the following three ways to pass, in order of priority: | |||||
| 1. Header: via HTTP Header `X-Trace-Id`, highest priority. | |||||
| 2. Query parameter: via URL query parameter `trace_id`. | |||||
| 3. Request Body: via request body field `trace_id` (i.e., this field). | |||||
| ### Response | ### Response | ||||
| When `response_mode` is `blocking`, return a CompletionResponse object. | When `response_mode` is `blocking`, return a CompletionResponse object. |
| ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用されます。 | ユーザー識別子、エンドユーザーのアイデンティティを定義するために使用されます。 | ||||
| アプリケーション内で開発者によって一意に定義される必要があります。 | アプリケーション内で開発者によって一意に定義される必要があります。 | ||||
| - `files` (array[object]) オプション | - `files` (array[object]) オプション | ||||
| - `trace_id` (string) オプション | |||||
| トレースID。既存の業務システムのトレースコンポーネントと連携し、エンドツーエンドの分散トレーシングを実現するために使用します。指定がない場合、システムが自動的に trace_id を生成します。以下の3つの方法で渡すことができ、優先順位は次のとおりです: | |||||
| 1. Header:HTTPヘッダー `X-Trace-Id` で渡す(最優先)。 | |||||
| 2. クエリパラメータ:URLクエリパラメータ `trace_id` で渡す。 | |||||
| 3. リクエストボディ:リクエストボディの `trace_id` フィールドで渡す(本フィールド)。 | |||||
| ### 応答 | ### 応答 |
| - `user` (string) Required | - `user` (string) Required | ||||
| 用户标识,用于定义终端用户的身份,方便检索、统计。 | 用户标识,用于定义终端用户的身份,方便检索、统计。 | ||||
| 由开发者定义规则,需保证用户标识在应用内唯一。API 无法访问 WebApp 创建的会话。 | 由开发者定义规则,需保证用户标识在应用内唯一。API 无法访问 WebApp 创建的会话。 | ||||
| - `files` (array[object]) 可选 | |||||
| - `trace_id` (string) Optional | |||||
| 链路追踪ID。适用于与业务系统已有的trace组件打通,实现端到端分布式追踪等场景。如果未指定,系统将自动生成 `trace_id`。支持以下三种方式传递,具体优先级依次为: | |||||
| 1. Header:推荐通过 HTTP Header `X-Trace-Id` 传递,优先级最高。 | |||||
| 2. Query 参数:通过 URL 查询参数 `trace_id` 传递。 | |||||
| 3. Request Body:通过请求体字段 `trace_id` 传递(即本字段)。 | |||||
| ### Response | ### Response | ||||
| 当 `response_mode` 为 `blocking` 时,返回 CompletionResponse object。 | 当 `response_mode` 为 `blocking` 时,返回 CompletionResponse object。 |