mirror of
https://github.com/langgenius/dify.git
synced 2026-06-23 12:31:13 +08:00
fix(agent): add stable debug conversation (#37744)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
7c20ffe6c4
commit
4065f63dce
@ -186,6 +186,7 @@ class AgentStatisticsQuery(BaseModel):
|
||||
|
||||
class AgentAppPartial(GenericAppPartial):
|
||||
app_id: str | None = None
|
||||
debug_conversation_id: str | None = None
|
||||
role: str | None = None
|
||||
active_config_is_published: bool = False
|
||||
published_reference_count: int = 0
|
||||
@ -194,6 +195,7 @@ class AgentAppPartial(GenericAppPartial):
|
||||
|
||||
class AgentAppDetailWithSite(GenericAppDetailWithSite):
|
||||
app_id: str | None = None
|
||||
debug_conversation_id: str | None = None
|
||||
role: str | None = None
|
||||
active_config_is_published: bool = False
|
||||
|
||||
@ -262,6 +264,7 @@ def _serialize_agent_app_detail(app_model) -> dict:
|
||||
payload.pop("bound_agent_id", None)
|
||||
payload["app_id"] = str(app_model.id)
|
||||
payload["id"] = agent.id
|
||||
payload["debug_conversation_id"] = agent.debug_conversation_id
|
||||
payload["role"] = agent.role or ""
|
||||
payload["active_config_is_published"] = roster_service.active_config_is_published(
|
||||
tenant_id=app_model.tenant_id,
|
||||
@ -301,6 +304,7 @@ def _serialize_agent_app_pagination(app_pagination, *, tenant_id: str) -> dict:
|
||||
if agent:
|
||||
item["app_id"] = app_id
|
||||
item["id"] = agent.id
|
||||
item["debug_conversation_id"] = agent.debug_conversation_id
|
||||
item["role"] = agent.role or ""
|
||||
item["active_config_is_published"] = active_config_is_published_by_agent_id.get(agent.id, False)
|
||||
published_references = published_references_by_agent_id.get(agent.id, [])
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
"""add agent debug conversation id
|
||||
|
||||
Revision ID: c8f4a6b2d3e1
|
||||
Revises: b2515f9d4c2a
|
||||
Create Date: 2026-06-22 10:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
import models
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "c8f4a6b2d3e1"
|
||||
down_revision = "b2515f9d4c2a"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table("agents", schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column("debug_conversation_id", models.types.StringUUID(), nullable=True))
|
||||
batch_op.create_index("agent_debug_conversation_id_idx", ["debug_conversation_id"], unique=False)
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table("agents", schema=None) as batch_op:
|
||||
batch_op.drop_index("agent_debug_conversation_id_idx")
|
||||
batch_op.drop_column("debug_conversation_id")
|
||||
@ -135,6 +135,7 @@ class Agent(DefaultFieldsMixin, Base):
|
||||
Index("agent_tenant_workflow_id_idx", "tenant_id", "workflow_id"),
|
||||
Index("agent_tenant_app_id_idx", "tenant_id", "app_id"),
|
||||
Index("agent_active_config_snapshot_id_idx", "active_config_snapshot_id"),
|
||||
Index("agent_debug_conversation_id_idx", "debug_conversation_id"),
|
||||
Index(
|
||||
"agent_tenant_invitable_idx",
|
||||
"tenant_id",
|
||||
@ -162,6 +163,7 @@ class Agent(DefaultFieldsMixin, Base):
|
||||
scope: Mapped[AgentScope] = mapped_column(EnumText(AgentScope, length=32), nullable=False)
|
||||
source: Mapped[AgentSource] = mapped_column(EnumText(AgentSource, length=32), nullable=False)
|
||||
app_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
debug_conversation_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
workflow_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
workflow_node_id: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
active_config_snapshot_id: Mapped[str | None] = mapped_column(StringUUID, nullable=True)
|
||||
|
||||
@ -12066,6 +12066,7 @@ Default namespace
|
||||
| bound_agent_id | string | | No |
|
||||
| created_at | integer | | No |
|
||||
| created_by | string | | No |
|
||||
| debug_conversation_id | string | | No |
|
||||
| deleted_tools | [ [DeletedTool](#deletedtool) ] | | No |
|
||||
| description | string | | No |
|
||||
| enable_api | boolean | | Yes |
|
||||
@ -12129,6 +12130,7 @@ default (the config form sends the full desired feature state on save).
|
||||
| create_user_name | string | | No |
|
||||
| created_at | integer | | No |
|
||||
| created_by | string | | No |
|
||||
| debug_conversation_id | string | | No |
|
||||
| description | string | | No |
|
||||
| has_draft_trigger | boolean | | No |
|
||||
| icon | string | | No |
|
||||
|
||||
@ -3,6 +3,7 @@ from typing import Any, TypedDict
|
||||
from sqlalchemy import and_, func, or_, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.helper import to_timestamp
|
||||
from models.agent import (
|
||||
@ -18,8 +19,8 @@ from models.agent import (
|
||||
WorkflowAgentNodeBinding,
|
||||
)
|
||||
from models.agent_config_entities import AgentSoulConfig
|
||||
from models.enums import AppStatus
|
||||
from models.model import App, AppMode, IconType
|
||||
from models.enums import AppStatus, ConversationFromSource, ConversationStatus
|
||||
from models.model import App, AppMode, Conversation, IconType
|
||||
from models.workflow import Workflow
|
||||
from services.agent.agent_soul_state import agent_soul_has_model
|
||||
from services.agent.composer_validator import ComposerConfigValidator
|
||||
@ -96,6 +97,7 @@ class AgentRosterService:
|
||||
"scope": agent.scope.value,
|
||||
"source": agent.source.value,
|
||||
"app_id": agent.app_id,
|
||||
"debug_conversation_id": getattr(agent, "debug_conversation_id", None),
|
||||
"workflow_id": agent.workflow_id,
|
||||
"workflow_node_id": agent.workflow_node_id,
|
||||
"active_config_snapshot_id": agent.active_config_snapshot_id,
|
||||
@ -391,9 +393,38 @@ class AgentRosterService:
|
||||
self._session.add(revision)
|
||||
agent.active_config_snapshot_id = version.id
|
||||
agent.active_config_has_model = agent_soul_has_model(AgentSoulConfig())
|
||||
agent.debug_conversation_id = self._create_agent_app_debug_conversation(
|
||||
app_id=app_id,
|
||||
account_id=account_id,
|
||||
)
|
||||
self._session.flush()
|
||||
return agent
|
||||
|
||||
def _create_agent_app_debug_conversation(self, *, app_id: str, account_id: str) -> str:
|
||||
"""Create the stable console conversation used by Agent App debug mode."""
|
||||
|
||||
conversation = Conversation(
|
||||
app_id=app_id,
|
||||
app_model_config_id=None,
|
||||
model_provider=None,
|
||||
model_id="",
|
||||
override_model_configs=None,
|
||||
mode=AppMode.AGENT,
|
||||
name="Agent Debugging Conversation",
|
||||
inputs={},
|
||||
introduction="",
|
||||
system_instruction="",
|
||||
system_instruction_tokens=0,
|
||||
status=ConversationStatus.NORMAL,
|
||||
invoke_from=InvokeFrom.DEBUGGER,
|
||||
from_source=ConversationFromSource.CONSOLE,
|
||||
from_end_user_id=None,
|
||||
from_account_id=account_id,
|
||||
)
|
||||
self._session.add(conversation)
|
||||
self._session.flush()
|
||||
return conversation.id
|
||||
|
||||
def load_app_backing_agents_by_app_id(self, *, tenant_id: str, app_ids: list[str]) -> dict[str, Agent]:
|
||||
"""Return active app-backed Agents keyed by Agent App id."""
|
||||
if not app_ids:
|
||||
|
||||
@ -219,14 +219,22 @@ def test_agent_app_list_and_create_use_agent_route(
|
||||
roster_controller.AgentRosterService,
|
||||
"load_app_backing_agents_by_app_id",
|
||||
lambda _self, **kwargs: {
|
||||
"app-list": SimpleNamespace(id="agent-list", role="List role", active_config_snapshot_id=None)
|
||||
"app-list": SimpleNamespace(
|
||||
id="agent-list",
|
||||
role="List role",
|
||||
debug_conversation_id="debug-conversation-list",
|
||||
active_config_snapshot_id=None,
|
||||
)
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
roster_controller.AgentRosterService,
|
||||
"get_app_backing_agent",
|
||||
lambda _self, **kwargs: SimpleNamespace(
|
||||
id="agent-created", role="Created role", active_config_snapshot_id=None
|
||||
id="agent-created",
|
||||
role="Created role",
|
||||
debug_conversation_id="debug-conversation-created",
|
||||
active_config_snapshot_id=None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
@ -263,6 +271,7 @@ def test_agent_app_list_and_create_use_agent_route(
|
||||
assert listed["total"] == 1
|
||||
assert listed["data"][0]["id"] == "agent-list"
|
||||
assert listed["data"][0]["app_id"] == "app-list"
|
||||
assert listed["data"][0]["debug_conversation_id"] == "debug-conversation-list"
|
||||
assert listed["data"][0]["role"] == "List role"
|
||||
assert listed["data"][0]["active_config_is_published"] is False
|
||||
assert listed["data"][0]["published_reference_count"] == 1
|
||||
@ -296,6 +305,7 @@ def test_agent_app_list_and_create_use_agent_route(
|
||||
assert status == 201
|
||||
assert created["id"] == "agent-created"
|
||||
assert created["app_id"] == "app-created"
|
||||
assert created["debug_conversation_id"] == "debug-conversation-created"
|
||||
assert created["role"] == "Created role"
|
||||
assert created["active_config_is_published"] is False
|
||||
assert "bound_agent_id" not in created
|
||||
@ -336,7 +346,12 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
|
||||
monkeypatch.setattr(
|
||||
roster_controller.AgentRosterService,
|
||||
"get_app_backing_agent",
|
||||
lambda _self, **kwargs: SimpleNamespace(id=agent_id, role="Resolved role", active_config_snapshot_id=None),
|
||||
lambda _self, **kwargs: SimpleNamespace(
|
||||
id=agent_id,
|
||||
role="Resolved role",
|
||||
debug_conversation_id="debug-conversation-detail",
|
||||
active_config_snapshot_id=None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
roster_controller.FeatureService,
|
||||
@ -361,6 +376,7 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
|
||||
detail = unwrap(AgentAppApi.get)(AgentAppApi(), "tenant-1", agent_id)
|
||||
assert detail["id"] == agent_id
|
||||
assert detail["app_id"] == "app-1"
|
||||
assert detail["debug_conversation_id"] == "debug-conversation-detail"
|
||||
assert detail["role"] == "Resolved role"
|
||||
assert detail["active_config_is_published"] is False
|
||||
assert "bound_agent_id" not in detail
|
||||
@ -374,6 +390,7 @@ def test_agent_app_detail_update_delete_resolve_app_from_agent_id(
|
||||
assert updated["name"] == "Renamed"
|
||||
assert updated["id"] == agent_id
|
||||
assert updated["app_id"] == "app-1"
|
||||
assert updated["debug_conversation_id"] == "debug-conversation-detail"
|
||||
assert updated["role"] == "Resolved role"
|
||||
assert updated["active_config_is_published"] is False
|
||||
assert "bound_agent_id" not in updated
|
||||
@ -445,7 +462,12 @@ def test_agent_app_update_rejects_empty_role(app: Flask, monkeypatch: pytest.Mon
|
||||
monkeypatch.setattr(
|
||||
roster_controller.AgentRosterService,
|
||||
"get_app_backing_agent",
|
||||
lambda _self, **kwargs: SimpleNamespace(id=agent_id, role="", active_config_snapshot_id=None),
|
||||
lambda _self, **kwargs: SimpleNamespace(
|
||||
id=agent_id,
|
||||
role="",
|
||||
debug_conversation_id="debug-conversation-detail",
|
||||
active_config_snapshot_id=None,
|
||||
),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
roster_controller.FeatureService,
|
||||
|
||||
@ -23,7 +23,8 @@ from models.agent_config_entities import (
|
||||
DeclaredOutputType,
|
||||
WorkflowNodeJobConfig,
|
||||
)
|
||||
from models.model import IconType
|
||||
from models.enums import ConversationFromSource, ConversationStatus
|
||||
from models.model import Conversation, IconType
|
||||
from models.workflow import Workflow
|
||||
from services.agent import composer_service, roster_service
|
||||
from services.agent.agent_soul_state import agent_soul_has_model
|
||||
@ -1353,6 +1354,7 @@ class TestAgentAppBackingAgent:
|
||||
assert agent.agent_kind == AgentKind.DIFY_AGENT
|
||||
assert agent.name == "Iris"
|
||||
assert agent.role == "research assistant"
|
||||
assert agent.debug_conversation_id is not None
|
||||
# A v1 snapshot + revision are seeded and wired as the active version.
|
||||
snapshots = [a for a in session.added if isinstance(a, AgentConfigSnapshot)]
|
||||
assert len(snapshots) == 1
|
||||
@ -1362,6 +1364,14 @@ class TestAgentAppBackingAgent:
|
||||
a for a in session.added if getattr(a, "operation", None) == AgentConfigRevisionOperation.CREATE_VERSION
|
||||
]
|
||||
assert len(revisions) == 1
|
||||
conversations = [a for a in session.added if isinstance(a, Conversation)]
|
||||
assert len(conversations) == 1
|
||||
assert agent.debug_conversation_id == conversations[0].id
|
||||
assert conversations[0].app_id == "app-1"
|
||||
assert conversations[0].mode == "agent"
|
||||
assert conversations[0].status == ConversationStatus.NORMAL
|
||||
assert conversations[0].from_source == ConversationFromSource.CONSOLE
|
||||
assert conversations[0].from_account_id == "account-1"
|
||||
# Caller (AppService.create_app) owns the commit — helper must not commit.
|
||||
assert session.commits == 0
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ export type AgentAppDetailWithSite = {
|
||||
bound_agent_id?: string | null
|
||||
created_at?: number | null
|
||||
created_by?: string | null
|
||||
debug_conversation_id?: string | null
|
||||
deleted_tools?: Array<DeletedTool>
|
||||
description?: string | null
|
||||
enable_api: boolean
|
||||
@ -329,6 +330,7 @@ export type AgentAppPartial = {
|
||||
create_user_name?: string | null
|
||||
created_at?: number | null
|
||||
created_by?: string | null
|
||||
debug_conversation_id?: string | null
|
||||
description?: string | null
|
||||
has_draft_trigger?: boolean | null
|
||||
icon?: string | null
|
||||
@ -1501,6 +1503,7 @@ export type AgentAppDetailWithSiteWritable = {
|
||||
bound_agent_id?: string | null
|
||||
created_at?: number | null
|
||||
created_by?: string | null
|
||||
debug_conversation_id?: string | null
|
||||
deleted_tools?: Array<DeletedTool>
|
||||
description?: string | null
|
||||
enable_api: boolean
|
||||
@ -1534,6 +1537,7 @@ export type AgentAppPartialWritable = {
|
||||
create_user_name?: string | null
|
||||
created_at?: number | null
|
||||
created_by?: string | null
|
||||
debug_conversation_id?: string | null
|
||||
description?: string | null
|
||||
has_draft_trigger?: boolean | null
|
||||
icon?: string | null
|
||||
|
||||
@ -684,6 +684,7 @@ export const zAgentAppPartial = z.object({
|
||||
create_user_name: z.string().nullish(),
|
||||
created_at: z.int().nullish(),
|
||||
created_by: z.string().nullish(),
|
||||
debug_conversation_id: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
has_draft_trigger: z.boolean().nullish(),
|
||||
icon: z.string().nullish(),
|
||||
@ -747,6 +748,7 @@ export const zAgentAppDetailWithSite = z.object({
|
||||
bound_agent_id: z.string().nullish(),
|
||||
created_at: z.int().nullish(),
|
||||
created_by: z.string().nullish(),
|
||||
debug_conversation_id: z.string().nullish(),
|
||||
deleted_tools: z.array(zDeletedTool).optional(),
|
||||
description: z.string().nullish(),
|
||||
enable_api: z.boolean(),
|
||||
@ -2086,6 +2088,7 @@ export const zAgentAppPartialWritable = z.object({
|
||||
create_user_name: z.string().nullish(),
|
||||
created_at: z.int().nullish(),
|
||||
created_by: z.string().nullish(),
|
||||
debug_conversation_id: z.string().nullish(),
|
||||
description: z.string().nullish(),
|
||||
has_draft_trigger: z.boolean().nullish(),
|
||||
icon: z.string().nullish(),
|
||||
@ -2150,6 +2153,7 @@ export const zAgentAppDetailWithSiteWritable = z.object({
|
||||
bound_agent_id: z.string().nullish(),
|
||||
created_at: z.int().nullish(),
|
||||
created_by: z.string().nullish(),
|
||||
debug_conversation_id: z.string().nullish(),
|
||||
deleted_tools: z.array(zDeletedTool).optional(),
|
||||
description: z.string().nullish(),
|
||||
enable_api: z.boolean(),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user