mirror of
https://github.com/langgenius/dify.git
synced 2026-06-24 13:01:16 +08:00
feat: add agent debug conversation refresh API (#37784)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
7852c273e4
commit
26639e0923
@ -228,6 +228,10 @@ class AgentAppDetailWithSite(GenericAppDetailWithSite):
|
||||
active_config_is_published: bool = False
|
||||
|
||||
|
||||
class AgentDebugConversationRefreshResponse(BaseModel):
|
||||
debug_conversation_id: str
|
||||
|
||||
|
||||
class AgentAppPagination(GenericAppPagination):
|
||||
data: list[AgentAppPartial] = Field( # type: ignore[assignment] # pyrefly: ignore[bad-override-mutable-attribute]
|
||||
validation_alias=AliasChoices("items", "data")
|
||||
@ -254,6 +258,7 @@ register_response_schema_models(
|
||||
AgentAppPublishedReferenceResponse,
|
||||
AgentAppDetailWithSite,
|
||||
AgentAppPartial,
|
||||
AgentDebugConversationRefreshResponse,
|
||||
AgentConfigSnapshotDetailResponse,
|
||||
AgentConfigSnapshotListResponse,
|
||||
AgentConfigSnapshotRestoreResponse,
|
||||
@ -535,6 +540,31 @@ class AgentAppApi(Resource):
|
||||
return "", 204
|
||||
|
||||
|
||||
@console_ns.route("/agent/<uuid:agent_id>/debug-conversation/refresh")
|
||||
class AgentDebugConversationRefreshApi(Resource):
|
||||
@console_ns.response(
|
||||
200,
|
||||
"Agent debug conversation refreshed",
|
||||
console_ns.models[AgentDebugConversationRefreshResponse.__name__],
|
||||
)
|
||||
@console_ns.response(403, "Insufficient permissions")
|
||||
@setup_required
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
@edit_permission_required
|
||||
@with_current_user
|
||||
@with_current_tenant_id
|
||||
def post(self, tenant_id: str, current_user: Account, agent_id: UUID):
|
||||
debug_conversation_id = _agent_roster_service().refresh_agent_app_debug_conversation_id(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=str(agent_id),
|
||||
account_id=current_user.id,
|
||||
)
|
||||
return AgentDebugConversationRefreshResponse(debug_conversation_id=debug_conversation_id).model_dump(
|
||||
mode="json"
|
||||
)
|
||||
|
||||
|
||||
@console_ns.route("/agent/<uuid:agent_id>/copy")
|
||||
class AgentAppCopyApi(Resource):
|
||||
@console_ns.expect(console_ns.models[CopyAppPayload.__name__])
|
||||
|
||||
@ -602,6 +602,20 @@ Stop a running Agent App chat message generation
|
||||
| 400 | Invalid request parameters | |
|
||||
| 403 | Insufficient permissions | |
|
||||
|
||||
### [POST] /agent/{agent_id}/debug-conversation/refresh
|
||||
#### Parameters
|
||||
|
||||
| Name | Located in | Description | Required | Schema |
|
||||
| ---- | ---------- | ----------- | -------- | ------ |
|
||||
| agent_id | path | | Yes | string (uuid) |
|
||||
|
||||
#### Responses
|
||||
|
||||
| Code | Description | Schema |
|
||||
| ---- | ----------- | ------ |
|
||||
| 200 | Agent debug conversation refreshed | **application/json**: [AgentDebugConversationRefreshResponse](#agentdebugconversationrefreshresponse)<br> |
|
||||
| 403 | Insufficient permissions | |
|
||||
|
||||
### [GET] /agent/{agent_id}/drive/files
|
||||
List agent drive entries for an Agent App
|
||||
|
||||
@ -12562,6 +12576,12 @@ Audit operation recorded for Agent Soul version/revision changes.
|
||||
| date | string | | Yes |
|
||||
| message_count | integer | | Yes |
|
||||
|
||||
#### AgentDebugConversationRefreshResponse
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
| ---- | ---- | ----------- | -------- |
|
||||
| debug_conversation_id | string | | Yes |
|
||||
|
||||
#### AgentDriveDeleteFileByAgentQuery
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|
||||
@ -492,6 +492,52 @@ class AgentRosterService:
|
||||
self._session.commit()
|
||||
return conversation_id
|
||||
|
||||
def refresh_agent_app_debug_conversation_id(
|
||||
self, *, tenant_id: str, agent_id: str, account_id: str, commit: bool = True
|
||||
) -> str:
|
||||
"""Start a new console debug conversation for the current Agent App editor."""
|
||||
|
||||
agent = self._session.scalar(
|
||||
select(Agent).where(
|
||||
Agent.tenant_id == tenant_id,
|
||||
Agent.id == agent_id,
|
||||
Agent.scope == AgentScope.ROSTER,
|
||||
Agent.source == AgentSource.AGENT_APP,
|
||||
Agent.status == AgentStatus.ACTIVE,
|
||||
)
|
||||
)
|
||||
if agent is None or not agent.app_id:
|
||||
raise AgentNotFoundError()
|
||||
|
||||
conversation_id = self._create_agent_app_debug_conversation(
|
||||
app_id=agent.app_id,
|
||||
account_id=account_id,
|
||||
)
|
||||
mapping = self._session.scalar(
|
||||
select(AgentDebugConversation).where(
|
||||
AgentDebugConversation.tenant_id == tenant_id,
|
||||
AgentDebugConversation.agent_id == agent_id,
|
||||
AgentDebugConversation.account_id == account_id,
|
||||
)
|
||||
)
|
||||
if mapping is None:
|
||||
self._session.add(
|
||||
AgentDebugConversation(
|
||||
tenant_id=tenant_id,
|
||||
agent_id=agent_id,
|
||||
app_id=agent.app_id,
|
||||
account_id=account_id,
|
||||
conversation_id=conversation_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
mapping.app_id = agent.app_id
|
||||
mapping.conversation_id = conversation_id
|
||||
self._session.flush()
|
||||
if commit:
|
||||
self._session.commit()
|
||||
return conversation_id
|
||||
|
||||
def load_or_create_agent_app_debug_conversation_ids_by_agent_id(
|
||||
self, *, tenant_id: str, agents: list[Agent], account_id: str
|
||||
) -> dict[str, str]:
|
||||
|
||||
@ -27,6 +27,7 @@ from controllers.console.agent.roster import (
|
||||
AgentAppApi,
|
||||
AgentAppCopyApi,
|
||||
AgentAppListApi,
|
||||
AgentDebugConversationRefreshApi,
|
||||
AgentInviteOptionsApi,
|
||||
AgentLogMessagesApi,
|
||||
AgentLogsApi,
|
||||
@ -158,6 +159,7 @@ def test_agent_v2_console_routes_are_agent_id_first() -> None:
|
||||
"/agent/<uuid:agent_id>/api-enable",
|
||||
"/agent/<uuid:agent_id>/api-keys",
|
||||
"/agent/<uuid:agent_id>/api-keys/<uuid:api_key_id>",
|
||||
"/agent/<uuid:agent_id>/debug-conversation/refresh",
|
||||
"/agent/<uuid:agent_id>/chat-messages",
|
||||
"/agent/<uuid:agent_id>/chat-messages/<string:task_id>/stop",
|
||||
"/agent/<uuid:agent_id>/feedbacks",
|
||||
@ -483,6 +485,38 @@ def test_agent_app_copy_uses_agent_id_and_returns_agent_detail(
|
||||
}
|
||||
|
||||
|
||||
def test_agent_debug_conversation_refresh_uses_current_user(
|
||||
app: Flask, monkeypatch: pytest.MonkeyPatch, account_id: str
|
||||
) -> None:
|
||||
agent_id = "00000000-0000-0000-0000-000000000001"
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
class FakeRosterService:
|
||||
def refresh_agent_app_debug_conversation_id(self, **kwargs: object) -> str:
|
||||
captured.update(kwargs)
|
||||
return "new-debug-conversation-id"
|
||||
|
||||
monkeypatch.setattr(roster_controller, "_agent_roster_service", lambda: FakeRosterService())
|
||||
|
||||
with app.test_request_context(
|
||||
"/console/api/agent/00000000-0000-0000-0000-000000000001/debug-conversation/refresh",
|
||||
method="POST",
|
||||
):
|
||||
response = unwrap(AgentDebugConversationRefreshApi.post)(
|
||||
AgentDebugConversationRefreshApi(),
|
||||
"tenant-1",
|
||||
SimpleNamespace(id=account_id),
|
||||
agent_id,
|
||||
)
|
||||
|
||||
assert response == {"debug_conversation_id": "new-debug-conversation-id"}
|
||||
assert captured == {
|
||||
"tenant_id": "tenant-1",
|
||||
"agent_id": agent_id,
|
||||
"account_id": account_id,
|
||||
}
|
||||
|
||||
|
||||
def test_agent_api_access_uses_agent_id_and_returns_service_api_metadata(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
|
||||
@ -1649,6 +1649,74 @@ class TestAgentAppBackingAgent:
|
||||
with pytest.raises(roster_service.AgentNotFoundError):
|
||||
service.get_agent_app_model(tenant_id="tenant-1", agent_id="agent-x")
|
||||
|
||||
def test_refresh_agent_app_debug_conversation_creates_mapping(self):
|
||||
agent = Agent(
|
||||
id="agent-1",
|
||||
tenant_id="tenant-1",
|
||||
name="Iris",
|
||||
description="",
|
||||
agent_kind=AgentKind.DIFY_AGENT,
|
||||
scope=AgentScope.ROSTER,
|
||||
source=AgentSource.AGENT_APP,
|
||||
status=AgentStatus.ACTIVE,
|
||||
app_id="app-1",
|
||||
)
|
||||
session = FakeSession(scalar=[agent, None])
|
||||
service = AgentRosterService(session)
|
||||
|
||||
conversation_id = service.refresh_agent_app_debug_conversation_id(
|
||||
tenant_id="tenant-1",
|
||||
agent_id="agent-1",
|
||||
account_id="account-1",
|
||||
)
|
||||
|
||||
conversations = [a for a in session.added if isinstance(a, Conversation)]
|
||||
assert len(conversations) == 1
|
||||
assert conversations[0].id == conversation_id
|
||||
assert conversations[0].app_id == "app-1"
|
||||
assert conversations[0].from_source == ConversationFromSource.CONSOLE
|
||||
assert conversations[0].from_account_id == "account-1"
|
||||
mappings = [a for a in session.added if isinstance(a, AgentDebugConversation)]
|
||||
assert len(mappings) == 1
|
||||
assert mappings[0].tenant_id == "tenant-1"
|
||||
assert mappings[0].agent_id == "agent-1"
|
||||
assert mappings[0].app_id == "app-1"
|
||||
assert mappings[0].account_id == "account-1"
|
||||
assert mappings[0].conversation_id == conversation_id
|
||||
assert session.deleted == []
|
||||
assert session.commits == 1
|
||||
|
||||
def test_refresh_agent_app_debug_conversation_replaces_existing_mapping(self):
|
||||
agent = Agent(
|
||||
id="agent-1",
|
||||
tenant_id="tenant-1",
|
||||
name="Iris",
|
||||
description="",
|
||||
agent_kind=AgentKind.DIFY_AGENT,
|
||||
scope=AgentScope.ROSTER,
|
||||
source=AgentSource.AGENT_APP,
|
||||
status=AgentStatus.ACTIVE,
|
||||
app_id="app-1",
|
||||
)
|
||||
mapping = SimpleNamespace(app_id="old-app", conversation_id="old-conversation")
|
||||
session = FakeSession(scalar=[agent, mapping])
|
||||
service = AgentRosterService(session)
|
||||
|
||||
conversation_id = service.refresh_agent_app_debug_conversation_id(
|
||||
tenant_id="tenant-1",
|
||||
agent_id="agent-1",
|
||||
account_id="account-1",
|
||||
)
|
||||
|
||||
assert mapping.app_id == "app-1"
|
||||
assert mapping.conversation_id == conversation_id
|
||||
assert [a for a in session.added if isinstance(a, AgentDebugConversation)] == []
|
||||
conversations = [a for a in session.added if isinstance(a, Conversation)]
|
||||
assert len(conversations) == 1
|
||||
assert conversations[0].id == conversation_id
|
||||
assert session.deleted == []
|
||||
assert session.commits == 1
|
||||
|
||||
def test_duplicate_agent_app_copies_app_config_and_active_soul(self, monkeypatch: pytest.MonkeyPatch):
|
||||
source_config = SimpleNamespace(
|
||||
opening_statement="hello",
|
||||
|
||||
@ -84,6 +84,8 @@ import {
|
||||
zPostAgentByAgentIdCopyBody,
|
||||
zPostAgentByAgentIdCopyPath,
|
||||
zPostAgentByAgentIdCopyResponse,
|
||||
zPostAgentByAgentIdDebugConversationRefreshPath,
|
||||
zPostAgentByAgentIdDebugConversationRefreshResponse,
|
||||
zPostAgentByAgentIdFeaturesBody,
|
||||
zPostAgentByAgentIdFeaturesPath,
|
||||
zPostAgentByAgentIdFeaturesResponse,
|
||||
@ -356,6 +358,25 @@ export const copy = {
|
||||
post: post5,
|
||||
}
|
||||
|
||||
export const post6 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
operationId: 'postAgentByAgentIdDebugConversationRefresh',
|
||||
path: '/agent/{agent_id}/debug-conversation/refresh',
|
||||
tags: ['console'],
|
||||
})
|
||||
.input(z.object({ params: zPostAgentByAgentIdDebugConversationRefreshPath }))
|
||||
.output(zPostAgentByAgentIdDebugConversationRefreshResponse)
|
||||
|
||||
export const refresh = {
|
||||
post: post6,
|
||||
}
|
||||
|
||||
export const debugConversation = {
|
||||
refresh,
|
||||
}
|
||||
|
||||
/**
|
||||
* Time-limited external signed URL for one Agent App drive value
|
||||
*/
|
||||
@ -481,7 +502,7 @@ export const drive = {
|
||||
/**
|
||||
* Update an Agent App's presentation features (opener, follow-up, citations, ...)
|
||||
*/
|
||||
export const post6 = oc
|
||||
export const post7 = oc
|
||||
.route({
|
||||
description: 'Update an Agent App\'s presentation features (opener, follow-up, citations, ...)',
|
||||
inputStructure: 'detailed',
|
||||
@ -496,13 +517,13 @@ export const post6 = oc
|
||||
.output(zPostAgentByAgentIdFeaturesResponse)
|
||||
|
||||
export const features = {
|
||||
post: post6,
|
||||
post: post7,
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update Agent App message feedback
|
||||
*/
|
||||
export const post7 = oc
|
||||
export const post8 = oc
|
||||
.route({
|
||||
description: 'Create or update Agent App message feedback',
|
||||
inputStructure: 'detailed',
|
||||
@ -517,7 +538,7 @@ export const post7 = oc
|
||||
.output(zPostAgentByAgentIdFeedbacksResponse)
|
||||
|
||||
export const feedbacks = {
|
||||
post: post7,
|
||||
post: post8,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -540,7 +561,7 @@ export const delete2 = oc
|
||||
/**
|
||||
* Commit an uploaded file into the Agent App drive under files/<name>
|
||||
*/
|
||||
export const post8 = oc
|
||||
export const post9 = oc
|
||||
.route({
|
||||
description: 'Commit an uploaded file into the Agent App drive under files/<name>',
|
||||
inputStructure: 'detailed',
|
||||
@ -555,7 +576,7 @@ export const post8 = oc
|
||||
|
||||
export const files2 = {
|
||||
delete: delete2,
|
||||
post: post8,
|
||||
post: post9,
|
||||
}
|
||||
|
||||
export const get13 = oc
|
||||
@ -684,7 +705,7 @@ export const read = {
|
||||
/**
|
||||
* Upload one Agent App sandbox file as a Dify ToolFile mapping
|
||||
*/
|
||||
export const post9 = oc
|
||||
export const post10 = oc
|
||||
.route({
|
||||
description: 'Upload one Agent App sandbox file as a Dify ToolFile mapping',
|
||||
inputStructure: 'detailed',
|
||||
@ -702,7 +723,7 @@ export const post9 = oc
|
||||
.output(zPostAgentByAgentIdSandboxFilesUploadResponse)
|
||||
|
||||
export const upload = {
|
||||
post: post9,
|
||||
post: post10,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -738,7 +759,7 @@ export const sandbox = {
|
||||
/**
|
||||
* Upload + standardize a Skill into an Agent App drive
|
||||
*/
|
||||
export const post10 = oc
|
||||
export const post11 = oc
|
||||
.route({
|
||||
description: 'Upload + standardize a Skill into an Agent App drive',
|
||||
inputStructure: 'detailed',
|
||||
@ -757,13 +778,13 @@ export const post10 = oc
|
||||
.output(zPostAgentByAgentIdSkillsUploadResponse)
|
||||
|
||||
export const upload2 = {
|
||||
post: post10,
|
||||
post: post11,
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer CLI tool + ENV suggestions from a standardized Agent App skill
|
||||
*/
|
||||
export const post11 = oc
|
||||
export const post12 = oc
|
||||
.route({
|
||||
description: 'Infer CLI tool + ENV suggestions from a standardized Agent App skill',
|
||||
inputStructure: 'detailed',
|
||||
@ -776,7 +797,7 @@ export const post11 = oc
|
||||
.output(zPostAgentByAgentIdSkillsBySlugInferToolsResponse)
|
||||
|
||||
export const inferTools = {
|
||||
post: post11,
|
||||
post: post12,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -828,7 +849,7 @@ export const statistics = {
|
||||
summary,
|
||||
}
|
||||
|
||||
export const post12 = oc
|
||||
export const post13 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -840,7 +861,7 @@ export const post12 = oc
|
||||
.output(zPostAgentByAgentIdVersionsByVersionIdRestoreResponse)
|
||||
|
||||
export const restore = {
|
||||
post: post12,
|
||||
post: post13,
|
||||
}
|
||||
|
||||
export const get21 = oc
|
||||
@ -919,6 +940,7 @@ export const byAgentId = {
|
||||
chatMessages,
|
||||
composer,
|
||||
copy,
|
||||
debugConversation,
|
||||
drive,
|
||||
features,
|
||||
feedbacks,
|
||||
@ -944,7 +966,7 @@ export const get24 = oc
|
||||
.input(z.object({ query: zGetAgentQuery.optional() }))
|
||||
.output(zGetAgentResponse)
|
||||
|
||||
export const post13 = oc
|
||||
export const post14 = oc
|
||||
.route({
|
||||
inputStructure: 'detailed',
|
||||
method: 'POST',
|
||||
@ -958,7 +980,7 @@ export const post13 = oc
|
||||
|
||||
export const agent = {
|
||||
get: get24,
|
||||
post: post13,
|
||||
post: post14,
|
||||
inviteOptions,
|
||||
byAgentId,
|
||||
}
|
||||
|
||||
@ -166,6 +166,10 @@ export type CopyAppPayload = {
|
||||
name?: string | null
|
||||
}
|
||||
|
||||
export type AgentDebugConversationRefreshResponse = {
|
||||
debug_conversation_id: string
|
||||
}
|
||||
|
||||
export type AgentDriveListResponse = {
|
||||
items?: Array<AgentDriveItemResponse>
|
||||
}
|
||||
@ -1968,6 +1972,26 @@ export type PostAgentByAgentIdCopyResponses = {
|
||||
export type PostAgentByAgentIdCopyResponse
|
||||
= PostAgentByAgentIdCopyResponses[keyof PostAgentByAgentIdCopyResponses]
|
||||
|
||||
export type PostAgentByAgentIdDebugConversationRefreshData = {
|
||||
body?: never
|
||||
path: {
|
||||
agent_id: string
|
||||
}
|
||||
query?: never
|
||||
url: '/agent/{agent_id}/debug-conversation/refresh'
|
||||
}
|
||||
|
||||
export type PostAgentByAgentIdDebugConversationRefreshErrors = {
|
||||
403: unknown
|
||||
}
|
||||
|
||||
export type PostAgentByAgentIdDebugConversationRefreshResponses = {
|
||||
200: AgentDebugConversationRefreshResponse
|
||||
}
|
||||
|
||||
export type PostAgentByAgentIdDebugConversationRefreshResponse
|
||||
= PostAgentByAgentIdDebugConversationRefreshResponses[keyof PostAgentByAgentIdDebugConversationRefreshResponses]
|
||||
|
||||
export type GetAgentByAgentIdDriveFilesData = {
|
||||
body?: never
|
||||
path: {
|
||||
|
||||
@ -61,6 +61,13 @@ export const zSimpleResultResponse = z.object({
|
||||
result: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* AgentDebugConversationRefreshResponse
|
||||
*/
|
||||
export const zAgentDebugConversationRefreshResponse = z.object({
|
||||
debug_conversation_id: z.string(),
|
||||
})
|
||||
|
||||
/**
|
||||
* AgentDriveDownloadResponse
|
||||
*/
|
||||
@ -2437,6 +2444,16 @@ export const zPostAgentByAgentIdCopyPath = z.object({
|
||||
*/
|
||||
export const zPostAgentByAgentIdCopyResponse = zAgentAppDetailWithSite
|
||||
|
||||
export const zPostAgentByAgentIdDebugConversationRefreshPath = z.object({
|
||||
agent_id: z.uuid(),
|
||||
})
|
||||
|
||||
/**
|
||||
* Agent debug conversation refreshed
|
||||
*/
|
||||
export const zPostAgentByAgentIdDebugConversationRefreshResponse
|
||||
= zAgentDebugConversationRefreshResponse
|
||||
|
||||
export const zGetAgentByAgentIdDriveFilesPath = z.object({
|
||||
agent_id: z.uuid(),
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user