mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-03-05 23:57:13 +08:00
Feat: published agent version control (#13410)
### What problem does this PR solve? Feat: published agent version control ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -75,6 +75,7 @@ async def rm():
|
||||
@login_required
|
||||
async def save():
|
||||
req = await get_request_json()
|
||||
req['release'] = bool(req.get("release", ""))
|
||||
try:
|
||||
req["dsl"] = CanvasReplicaService.normalize_dsl(req["dsl"])
|
||||
except ValueError as e:
|
||||
|
||||
@ -83,7 +83,9 @@ async def create(tenant_id, chat_id):
|
||||
@manager.route("/agents/<agent_id>/sessions", methods=["POST"]) # noqa: F821
|
||||
@token_required
|
||||
async def create_agent_session(tenant_id, agent_id):
|
||||
user_id = request.args.get("user_id", tenant_id)
|
||||
req = await get_request_json()
|
||||
user_id = req.get("user_id") or request.args.get("user_id", tenant_id)
|
||||
release_mode = req.get("release", request.args.get("release", False))
|
||||
e, cvs = UserCanvasService.get_by_id(agent_id)
|
||||
if not e:
|
||||
return get_error_data_result("Agent not found.")
|
||||
@ -92,6 +94,8 @@ async def create_agent_session(tenant_id, agent_id):
|
||||
if not isinstance(cvs.dsl, str):
|
||||
cvs.dsl = json.dumps(cvs.dsl, ensure_ascii=False)
|
||||
|
||||
if release_mode and not bool(cvs.release):
|
||||
raise PermissionError("No available published version")
|
||||
session_id = get_uuid()
|
||||
canvas = Canvas(cvs.dsl, tenant_id, agent_id, canvas_id=cvs.id)
|
||||
canvas.reset()
|
||||
@ -986,15 +990,35 @@ async def agent_bot_completions(agent_id):
|
||||
return get_error_data_result(message='Authentication error: API key is invalid!"')
|
||||
|
||||
if req.get("stream", True):
|
||||
resp = Response(agent_completion(objs[0].tenant_id, agent_id, **req), mimetype="text/event-stream")
|
||||
async def stream():
|
||||
try:
|
||||
async for answer in agent_completion(objs[0].tenant_id, agent_id, **req):
|
||||
yield answer
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
error_result = get_error_data_result(message=str(e) or "Unknown error")
|
||||
yield "data:" + json.dumps(
|
||||
{
|
||||
"event": "message",
|
||||
"data": {"content": f"Error {error_result['code']}: {error_result['message']}\n\n"},
|
||||
**error_result,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
) + "\n\n"
|
||||
|
||||
resp = Response(stream(), mimetype="text/event-stream")
|
||||
resp.headers.add_header("Cache-control", "no-cache")
|
||||
resp.headers.add_header("Connection", "keep-alive")
|
||||
resp.headers.add_header("X-Accel-Buffering", "no")
|
||||
resp.headers.add_header("Content-Type", "text/event-stream; charset=utf-8")
|
||||
return resp
|
||||
|
||||
async for answer in agent_completion(objs[0].tenant_id, agent_id, **req):
|
||||
return get_result(data=answer)
|
||||
try:
|
||||
async for answer in agent_completion(objs[0].tenant_id, agent_id, **req):
|
||||
return get_result(data=answer)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
return get_error_data_result(message=str(e) or "Unknown error")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@ -1032,6 +1032,7 @@ class UserCanvas(DataBaseModel):
|
||||
title = CharField(max_length=255, null=True, help_text="Canvas title")
|
||||
|
||||
permission = CharField(max_length=16, null=False, help_text="me|team", default="me", index=True)
|
||||
release = BooleanField(null=False, help_text="is released", default=False, index=True)
|
||||
description = TextField(null=True, help_text="Canvas description")
|
||||
canvas_type = CharField(max_length=32, null=True, help_text="Canvas type", index=True)
|
||||
canvas_category = CharField(max_length=32, null=False, default="agent_canvas", help_text="Canvas category: agent_canvas|dataflow_canvas", index=True)
|
||||
@ -1407,6 +1408,7 @@ def migrate_db():
|
||||
alter_db_add_column(migrator, "task", "task_type", CharField(max_length=32, null=False, default=""))
|
||||
alter_db_add_column(migrator, "task", "priority", IntegerField(default=0))
|
||||
alter_db_add_column(migrator, "user_canvas", "permission", CharField(max_length=16, null=False, help_text="me|team", default="me", index=True))
|
||||
alter_db_add_column(migrator, "user_canvas", "release", BooleanField(null=False, help_text="is released", default=False, index=True))
|
||||
alter_db_add_column(migrator, "llm", "is_tools", BooleanField(null=False, help_text="support tools", default=False))
|
||||
alter_db_add_column(migrator, "mcp_server", "variables", JSONField(null=True, help_text="MCP Server variables", default=dict))
|
||||
alter_db_rename_column(migrator, "task", "process_duation", "process_duration")
|
||||
|
||||
@ -195,10 +195,12 @@ async def completion(tenant_id, agent_id, session_id=None, **kwargs):
|
||||
inputs = kwargs.get("inputs", {})
|
||||
user_id = kwargs.get("user_id", "")
|
||||
custom_header = kwargs.get("custom_header", "")
|
||||
release_mode = str(kwargs.get("release", "")).strip().lower()
|
||||
|
||||
if session_id:
|
||||
e, conv = API4ConversationService.get_by_id(session_id)
|
||||
assert e, "Session not found!"
|
||||
if not e:
|
||||
raise LookupError("Session not found!")
|
||||
if not conv.message:
|
||||
conv.message = []
|
||||
if not isinstance(conv.dsl, str):
|
||||
@ -206,10 +208,15 @@ async def completion(tenant_id, agent_id, session_id=None, **kwargs):
|
||||
canvas = Canvas(conv.dsl, tenant_id, agent_id, canvas_id=agent_id, custom_header=custom_header)
|
||||
else:
|
||||
e, cvs = UserCanvasService.get_by_id(agent_id)
|
||||
assert e, "Agent not found."
|
||||
assert cvs.user_id == tenant_id, "You do not own the agent."
|
||||
if not e:
|
||||
raise LookupError("Agent not found.")
|
||||
if cvs.user_id != tenant_id:
|
||||
raise PermissionError("You do not own the agent.")
|
||||
if release_mode == "true" and not bool(cvs.release):
|
||||
raise PermissionError("No available published version")
|
||||
if not isinstance(cvs.dsl, str):
|
||||
cvs.dsl = json.dumps(cvs.dsl, ensure_ascii=False)
|
||||
|
||||
session_id=get_uuid()
|
||||
canvas = Canvas(cvs.dsl, tenant_id, agent_id, canvas_id=cvs.id, custom_header=custom_header)
|
||||
canvas.reset()
|
||||
|
||||
@ -3682,6 +3682,7 @@ Asks a specified agent a question to start an AI-powered conversation.
|
||||
- `"inputs"`: `object` (optional)
|
||||
- `"user_id"`: `string` (optional)
|
||||
- `"return_trace"`: `boolean` (optional, default `false`) — include execution trace logs.
|
||||
- `"release"`: `boolean` (optional, default `false`) - whether to visit the latest published canvas.
|
||||
|
||||
#### Streaming events to handle
|
||||
|
||||
|
||||
@ -1593,6 +1593,10 @@ Creates a session with the current agent.
|
||||
|
||||
The parameters in `begin` component.
|
||||
|
||||
Also supports:
|
||||
|
||||
- `release` (`bool | str`, optional): Set to `True` (or `"true"`) to create the session in release mode (published version only).
|
||||
|
||||
#### Returns
|
||||
|
||||
- Success: A `Session` object containing the following attributes:
|
||||
@ -1610,6 +1614,8 @@ rag_object = RAGFlow(api_key="<YOUR_API_KEY>", base_url="http://<YOUR_BASE_URL>:
|
||||
agent_id = "AGENT_ID"
|
||||
agent = rag_object.list_agents(id = agent_id)[0]
|
||||
session = agent.create_session()
|
||||
# Or create in release mode:
|
||||
# session = agent.create_session(release=True)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@ -36,6 +36,7 @@ import { z } from 'zod';
|
||||
|
||||
const FormSchema = z.object({
|
||||
visibleAvatar: z.boolean(),
|
||||
publishAvatar: z.boolean(),
|
||||
locale: z.string(),
|
||||
embedType: z.enum(['fullscreen', 'widget']),
|
||||
enableStreaming: z.boolean(),
|
||||
@ -62,6 +63,7 @@ function EmbedDialog({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
visibleAvatar: false,
|
||||
publishAvatar: false,
|
||||
locale: '',
|
||||
embedType: 'fullscreen' as const,
|
||||
enableStreaming: false,
|
||||
@ -79,7 +81,14 @@ function EmbedDialog({
|
||||
}, []);
|
||||
|
||||
const generateIframeSrc = useCallback(() => {
|
||||
const { visibleAvatar, locale, embedType, enableStreaming, theme } = values;
|
||||
const {
|
||||
visibleAvatar,
|
||||
publishAvatar,
|
||||
locale,
|
||||
embedType,
|
||||
enableStreaming,
|
||||
theme,
|
||||
} = values;
|
||||
const baseRoute =
|
||||
embedType === 'widget'
|
||||
? Routes.ChatWidget
|
||||
@ -87,6 +96,9 @@ function EmbedDialog({
|
||||
? Routes.AgentShare
|
||||
: Routes.ChatShare;
|
||||
let src = `${location.origin}${baseRoute}?shared_id=${token}&from=${from}&auth=${beta}`;
|
||||
if (publishAvatar) {
|
||||
src += '&release=true';
|
||||
}
|
||||
if (visibleAvatar) {
|
||||
src += '&visible_avatar=1';
|
||||
}
|
||||
@ -245,6 +257,22 @@ function EmbedDialog({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="publishAvatar"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Publish Avatar</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
></Switch>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{values.embedType === 'widget' && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@ -145,7 +145,7 @@ export const useSendMessageBySSE = (url: string = api.completeConversation) => {
|
||||
const val = JSON.parse(value?.data || '');
|
||||
|
||||
console.info('data:', val);
|
||||
if (val.code === 500) {
|
||||
if (typeof val?.code === 'number' && val.code !== 0) {
|
||||
message.error(val.message);
|
||||
}
|
||||
|
||||
|
||||
@ -105,7 +105,13 @@ export function findInputFromList(eventList: IEventList) {
|
||||
}
|
||||
|
||||
export function getLatestError(eventList: IEventList) {
|
||||
return get(eventList.at(-1), 'data.outputs._ERROR');
|
||||
const latest = eventList.at(-1) as
|
||||
| { code?: number; message?: string }
|
||||
| undefined;
|
||||
return (
|
||||
get(latest, 'data.outputs._ERROR') ||
|
||||
(latest?.code && latest.code !== 0 ? latest?.message : undefined)
|
||||
);
|
||||
}
|
||||
|
||||
export const useGetBeginNodePrologue = () => {
|
||||
@ -218,13 +224,15 @@ export const useSendAgentMessage = ({
|
||||
isShared,
|
||||
refetch,
|
||||
isTaskMode: isTask,
|
||||
releaseMode,
|
||||
}: {
|
||||
url?: string;
|
||||
addEventList?: (data: IEventList, messageId: string) => void;
|
||||
beginParams?: any[];
|
||||
beginParams?: BeginQuery[];
|
||||
isShared?: boolean;
|
||||
refetch?: () => void;
|
||||
isTaskMode?: boolean;
|
||||
releaseMode?: string | null;
|
||||
}) => {
|
||||
const { id: agentId } = useParams();
|
||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||
@ -232,9 +240,10 @@ export const useSendAgentMessage = ({
|
||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
||||
const { send, answerList, done, stopOutputMessage, resetAnswerList } =
|
||||
useSendMessageBySSE(url || api.runCanvas);
|
||||
const firstAnswer = answerList[0];
|
||||
const messageId = useMemo(() => {
|
||||
return answerList[0]?.message_id;
|
||||
}, [answerList]);
|
||||
return firstAnswer?.message_id;
|
||||
}, [firstAnswer]);
|
||||
|
||||
const isTaskMode = useIsTaskMode(isTask);
|
||||
|
||||
@ -266,12 +275,12 @@ export const useSendAgentMessage = ({
|
||||
const { stopMessage } = useStopMessage();
|
||||
|
||||
const stopConversation = useCallback(() => {
|
||||
const taskId = answerList.at(0)?.task_id;
|
||||
const taskId = firstAnswer?.task_id;
|
||||
stopOutputMessage();
|
||||
if (!isShared) {
|
||||
stopMessage(taskId);
|
||||
}
|
||||
}, [answerList, isShared, stopMessage, stopOutputMessage]);
|
||||
}, [firstAnswer, isShared, stopMessage, stopOutputMessage]);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async ({
|
||||
@ -303,6 +312,9 @@ export const useSendAgentMessage = ({
|
||||
params.files = uploadResponseList;
|
||||
|
||||
params.session_id = sessionId || exploreSessionId;
|
||||
if (releaseMode) {
|
||||
params.release = releaseMode;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@ -334,6 +346,7 @@ export const useSendAgentMessage = ({
|
||||
setValue,
|
||||
removeLatestMessage,
|
||||
refetch,
|
||||
releaseMode,
|
||||
],
|
||||
);
|
||||
|
||||
@ -345,10 +358,14 @@ export const useSendAgentMessage = ({
|
||||
.join('<br/>'),
|
||||
role: MessageType.User,
|
||||
});
|
||||
await send({ ...body, session_id: sessionId });
|
||||
await send({
|
||||
...body,
|
||||
session_id: sessionId,
|
||||
...(releaseMode ? { release: releaseMode } : {}),
|
||||
});
|
||||
refetch?.();
|
||||
},
|
||||
[addNewestOneQuestion, refetch, send, sessionId],
|
||||
[addNewestOneQuestion, refetch, releaseMode, send, sessionId],
|
||||
);
|
||||
|
||||
// reset session
|
||||
@ -396,7 +413,7 @@ export const useSendAgentMessage = ({
|
||||
],
|
||||
);
|
||||
|
||||
const sendedTaskMessage = useRef<boolean>(false);
|
||||
const sendedTaskMessage = useRef(false);
|
||||
|
||||
const sendMessageInTaskMode = useCallback(() => {
|
||||
if (isShared || !isTaskMode || sendedTaskMessage.current) {
|
||||
@ -457,10 +474,10 @@ export const useSendAgentMessage = ({
|
||||
}, [addEventList, answerList, addEventListFun, messageId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (answerList[0]?.session_id) {
|
||||
setSessionId(answerList[0]?.session_id);
|
||||
if (firstAnswer?.session_id) {
|
||||
setSessionId(firstAnswer.session_id);
|
||||
}
|
||||
}, [answerList]);
|
||||
}, [firstAnswer]);
|
||||
|
||||
return {
|
||||
value,
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
buildRequestBody,
|
||||
useSendAgentMessage,
|
||||
} from '@/pages/agent/chat/use-send-agent-message';
|
||||
import { BeginQuery } from '@/pages/agent/interface';
|
||||
import { isEmpty } from 'lodash';
|
||||
import trim from 'lodash/trim';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
@ -16,36 +17,54 @@ export const useSendButtonDisabled = (value: string) => {
|
||||
return trim(value) === '';
|
||||
};
|
||||
|
||||
const DATA_PREFIX = 'data_';
|
||||
|
||||
interface SharedChatSearchParams {
|
||||
from: SharedFrom;
|
||||
sharedId: string | null;
|
||||
release: string | null;
|
||||
locale: string | null;
|
||||
theme: string | null;
|
||||
data: Record<string, string>;
|
||||
visibleAvatar: boolean;
|
||||
}
|
||||
|
||||
export const useGetSharedChatSearchParams = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const data_prefix = 'data_';
|
||||
const data = Object.fromEntries(
|
||||
searchParams
|
||||
.entries()
|
||||
.filter(([key]) => key.startsWith(data_prefix))
|
||||
.map(([key, value]) => [key.replace(data_prefix, ''), value]),
|
||||
.filter(([key]) => key.startsWith(DATA_PREFIX))
|
||||
.map(([key, value]) => [key.replace(DATA_PREFIX, ''), value]),
|
||||
);
|
||||
return {
|
||||
from: searchParams.get('from') as SharedFrom,
|
||||
sharedId: searchParams.get('shared_id'),
|
||||
release: searchParams.get('release'),
|
||||
locale: searchParams.get('locale'),
|
||||
theme: searchParams.get('theme'),
|
||||
data: data,
|
||||
data,
|
||||
visibleAvatar: searchParams.get('visible_avatar')
|
||||
? searchParams.get('visible_avatar') !== '1'
|
||||
: true,
|
||||
};
|
||||
} as SharedChatSearchParams;
|
||||
};
|
||||
|
||||
export const useSendNextSharedMessage = (
|
||||
addEventList: (data: IEventList, messageId: string) => void,
|
||||
) => {
|
||||
const { from, sharedId: conversationId } = useGetSharedChatSearchParams();
|
||||
const url = `/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`;
|
||||
const {
|
||||
from,
|
||||
sharedId: conversationId,
|
||||
release,
|
||||
} = useGetSharedChatSearchParams();
|
||||
const botType = from === SharedFrom.Agent ? 'agentbots' : 'chatbots';
|
||||
const releaseQuery = release ? `?release=${encodeURIComponent(release)}` : '';
|
||||
const url = `/api/v1/${botType}/${conversationId}/completions${releaseQuery}`;
|
||||
const { data: inputsData } = useFetchExternalAgentInputs();
|
||||
|
||||
const [params, setParams] = useState<any[]>([]);
|
||||
const sendedTaskMessage = useRef<boolean>(false);
|
||||
const [params, setParams] = useState<BeginQuery[]>([]);
|
||||
const sendedTaskMessage = useRef(false);
|
||||
|
||||
const isTaskMode = inputsData.mode === AgentDialogueMode.Task;
|
||||
|
||||
@ -61,10 +80,10 @@ export const useSendNextSharedMessage = (
|
||||
beginParams: params,
|
||||
isShared: true,
|
||||
isTaskMode,
|
||||
releaseMode: release,
|
||||
});
|
||||
|
||||
const ok = useCallback(
|
||||
(params: any[]) => {
|
||||
(params: BeginQuery[]) => {
|
||||
if (isTaskMode) {
|
||||
const msgBody = buildRequestBody('');
|
||||
|
||||
|
||||
@ -21,38 +21,53 @@ export const useSendButtonDisabled = (value: string) => {
|
||||
return trim(value) === '';
|
||||
};
|
||||
|
||||
const DATA_PREFIX = 'data_';
|
||||
|
||||
interface SharedChatSearchParams {
|
||||
from: SharedFrom;
|
||||
sharedId: string | null;
|
||||
release: string | null;
|
||||
locale: string | null;
|
||||
theme: string | null;
|
||||
data: Record<string, string>;
|
||||
visibleAvatar: boolean;
|
||||
}
|
||||
|
||||
export const useGetSharedChatSearchParams = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const data_prefix = 'data_';
|
||||
const data = Object.fromEntries(
|
||||
Array.from(searchParams.entries())
|
||||
.filter(([key]) => key.startsWith(data_prefix))
|
||||
.map(([key, value]) => [key.replace(data_prefix, ''), value]),
|
||||
.filter(([key]) => key.startsWith(DATA_PREFIX))
|
||||
.map(([key, value]) => [key.replace(DATA_PREFIX, ''), value]),
|
||||
);
|
||||
return {
|
||||
from: searchParams.get('from') as SharedFrom,
|
||||
sharedId: searchParams.get('shared_id'),
|
||||
release: searchParams.get('release'),
|
||||
locale: searchParams.get('locale'),
|
||||
theme: searchParams.get('theme'),
|
||||
data: data,
|
||||
visibleAvatar: searchParams.get('visible_avatar')
|
||||
? searchParams.get('visible_avatar') !== '1'
|
||||
: true,
|
||||
};
|
||||
} as SharedChatSearchParams;
|
||||
};
|
||||
|
||||
export const useSendSharedMessage = () => {
|
||||
const {
|
||||
from,
|
||||
sharedId: conversationId,
|
||||
data: data,
|
||||
release,
|
||||
data: sharedData,
|
||||
} = useGetSharedChatSearchParams();
|
||||
const botType = from === SharedFrom.Agent ? 'agentbots' : 'chatbots';
|
||||
const releaseQuery = release ? `?release=${encodeURIComponent(release)}` : '';
|
||||
const completionUrl = `/api/v1/${botType}/${conversationId}/completions${releaseQuery}`;
|
||||
const { createSharedConversation: setConversation } =
|
||||
useCreateNextSharedConversation();
|
||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||
const { send, answer, done, stopOutputMessage } = useSendMessageWithSse(
|
||||
`/api/v1/${from === SharedFrom.Agent ? 'agentbots' : 'chatbots'}/${conversationId}/completions`,
|
||||
);
|
||||
const { send, answer, done, stopOutputMessage } =
|
||||
useSendMessageWithSse(completionUrl);
|
||||
const {
|
||||
derivedMessages,
|
||||
removeLatestMessage,
|
||||
@ -79,6 +94,7 @@ export const useSendSharedMessage = () => {
|
||||
session_id: get(derivedMessages, '0.session_id'),
|
||||
reasoning: enableThinking,
|
||||
internet: enableInternet,
|
||||
...(release ? { release } : {}),
|
||||
});
|
||||
|
||||
if (isCompletionError(res)) {
|
||||
@ -87,7 +103,14 @@ export const useSendSharedMessage = () => {
|
||||
removeLatestMessage();
|
||||
}
|
||||
},
|
||||
[send, conversationId, derivedMessages, setValue, removeLatestMessage],
|
||||
[
|
||||
send,
|
||||
conversationId,
|
||||
derivedMessages,
|
||||
setValue,
|
||||
removeLatestMessage,
|
||||
release,
|
||||
],
|
||||
);
|
||||
|
||||
const handleSendMessage = useCallback(
|
||||
@ -111,12 +134,16 @@ export const useSendSharedMessage = () => {
|
||||
|
||||
const fetchSessionId = useCallback(async () => {
|
||||
const payload = { question: '' };
|
||||
const ret = await send({ ...payload, ...data });
|
||||
const ret = await send({
|
||||
...payload,
|
||||
...sharedData,
|
||||
...(release ? { release } : {}),
|
||||
});
|
||||
if (isCompletionError(ret)) {
|
||||
message.error(ret?.data.message);
|
||||
setHasError(true);
|
||||
}
|
||||
}, [send]);
|
||||
}, [sharedData, release, send]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSessionId();
|
||||
|
||||
Reference in New Issue
Block a user