浏览代码

add show debug (#7390)

### What problem does this PR solve?

add show debug
![Recording2025-04-28142829-ezgif
com-video-to-gif-converter](https://github.com/user-attachments/assets/0c67da34-c2b6-428f-ae9b-b5b21464885c)

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
tags/v0.19.0
so95 5 个月前
父节点
当前提交
514c08a932
没有帐户链接到提交者的电子邮件

+ 2
- 0
.gitignore 查看文件

@@ -42,3 +42,5 @@ nltk_data/
# Exclude hash-like temporary files like 9b5ad71b2ce5302211f9c61530b329a4922fc6a4
*[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*
.lh/
.venv
docker/data

+ 4
- 2
agent/canvas.py 查看文件

@@ -17,7 +17,6 @@ import logging
import json
from copy import deepcopy
from functools import partial

import pandas as pd

from agent.component import component_class
@@ -362,4 +361,7 @@ class Canvas:
return self.components["begin"]["obj"]._param.query

def get_component_input_elements(self, cpnnm):
return self.components[cpnnm]["obj"].get_input_elements()
return self.components[cpnnm]["obj"].get_input_elements()
def set_component_infor(self, cpn_id, infor):
self.components[cpn_id]["obj"].set_infor(infor)

+ 4
- 1
agent/component/base.py 查看文件

@@ -34,6 +34,7 @@ _IS_RAW_CONF = "_is_raw_conf"
class ComponentParamBase(ABC):
def __init__(self):
self.output_var_name = "output"
self.infor_var_name = "infor"
self.message_history_window_size = 22
self.query = []
self.inputs = []
@@ -462,6 +463,9 @@ class ComponentBase(ABC):
def set_output(self, v):
setattr(self._param, self._param.output_var_name, v)

def set_infor(self, v):
setattr(self._param, self._param.infor_var_name, v)
def _fetch_outputs_from(self, sources: list[dict[str, Any]]) -> list[pd.DataFrame]:
outs = []
for q in sources:
@@ -488,7 +492,6 @@ class ComponentBase(ABC):
elif q.get("value"):
outs.append(pd.DataFrame([{"content": q["value"]}]))
return outs

def get_input(self):
if self._param.debug_inputs:
return pd.DataFrame([{"content": v["value"]} for v in self._param.debug_inputs if v.get("value")])

+ 2
- 0
agent/component/categorize.py 查看文件

@@ -85,6 +85,8 @@ class Categorize(Generate, ABC):
input = self.get_input()
input = " - ".join(input["content"]) if "content" in input else ""
chat_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.CHAT, self._param.llm_id)
self._canvas.set_component_infor(self._id, {"prompt":self._param.get_prompt(input),"messages": [{"role": "user", "content": "\nCategory: "}],"conf": self._param.gen_conf()})

ans = chat_mdl.chat(self._param.get_prompt(input), [{"role": "user", "content": "\nCategory: "}],
self._param.gen_conf())
logging.debug(f"input: {input}, answer: {str(ans)}")

+ 2
- 2
agent/component/generate.py 查看文件

@@ -201,7 +201,7 @@ class Generate(ComponentBase):
msg.append({"role": "user", "content": "Output: "})
ans = chat_mdl.chat(msg[0]["content"], msg[1:], self._param.gen_conf())
ans = re.sub(r"^.*</think>", "", ans, flags=re.DOTALL)
self._canvas.set_component_infor(self._id, {"prompt":msg[0]["content"],"messages": msg[1:],"conf": self._param.gen_conf()})
if self._param.cite and "chunks" in retrieval_res.columns:
res = self.set_cite(retrieval_res, ans)
return pd.DataFrame([res])
@@ -234,7 +234,7 @@ class Generate(ComponentBase):
if self._param.cite and "chunks" in retrieval_res.columns:
res = self.set_cite(retrieval_res, answer)
yield res
self._canvas.set_component_infor(self._id, {"prompt":msg[0]["content"],"messages": msg[1:],"conf": self._param.gen_conf()})
self.set_output(Generate.be_output(res))

def debug(self, **kwargs):

+ 2
- 0
agent/component/keyword.py 查看文件

@@ -58,6 +58,8 @@ class KeywordExtract(Generate, ABC):


chat_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.CHAT, self._param.llm_id)
self._canvas.set_component_infor(self._id, {"prompt":self._param.get_prompt(),"messages": [{"role": "user", "content": query}],"conf": self._param.gen_conf()})

ans = chat_mdl.chat(self._param.get_prompt(), [{"role": "user", "content": query}],
self._param.gen_conf())


+ 0
- 2
api/apps/canvas_app.py 查看文件

@@ -26,7 +26,6 @@ from api.utils.api_utils import get_json_result, server_error_response, validate
from agent.canvas import Canvas
from peewee import MySQLDatabase, PostgresqlDatabase
from api.db.db_models import APIToken
import logging
import time

@manager.route('/templates', methods=['GET']) # noqa: F821
@@ -89,7 +88,6 @@ def save():
@login_required
def get(canvas_id):
e, c = UserCanvasService.get_by_tenant_id(canvas_id)
logging.info(f"get canvas_id: {canvas_id} c: {c}")
if not e:
return get_data_error_result(message="canvas not found.")
return get_json_result(data=c)

+ 1
- 0
web/src/locales/en.ts 查看文件

@@ -1258,6 +1258,7 @@ This delimiter is used to split the input text into several text pieces echo of
promptTip:
'Use the system prompt to describe the task for the LLM, specify how it should respond, and outline other miscellaneous requirements. The system prompt is often used in conjunction with keys (variables), which serve as various data inputs for the LLM. Use a forward slash `/` or the (x) button to show the keys to use.',
promptMessage: 'Prompt is required',
infor: 'Information run',
knowledgeBasesTip:
'Select the knowledge bases to associate with this chat assistant, or choose variables containing knowledge base IDs below.',
knowledgeBaseVars: 'Knowledge base variables',

+ 250
- 58
web/src/pages/flow/canvas/node/popover.tsx 查看文件

@@ -1,11 +1,15 @@
import { useFetchFlow } from '@/hooks/flow-hooks';
import get from 'lodash/get';
import React, { MouseEventHandler, useCallback, useMemo } from 'react';
import React, {
MouseEventHandler,
useCallback,
useMemo,
useState,
} from 'react';
import JsonView from 'react18-json-view';
import 'react18-json-view/src/style.css';
import { useReplaceIdWithText } from '../../hooks';

import { useTheme } from '@/components/theme-provider';
import {
Popover,
PopoverContent,
@@ -20,6 +24,17 @@ import {
TableRow,
} from '@/components/ui/table';
import { useTranslate } from '@/hooks/common-hooks';
import {
Button,
Card,
Col,
Input,
Row,
Space,
Tabs,
Typography,
message,
} from 'antd';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';

interface IProps extends React.PropsWithChildren {
@@ -31,7 +46,8 @@ export function NextNodePopover({ children, nodeId, name }: IProps) {
const { t } = useTranslate('flow');

const { data } = useFetchFlow();
const { theme } = useTheme();
console.log(data);

const component = useMemo(() => {
return get(data, ['dsl', 'components', nodeId], {});
}, [nodeId, data]);
@@ -42,6 +58,11 @@ export function NextNodePopover({ children, nodeId, name }: IProps) {
[],
);
const output = get(component, ['obj', 'output'], {});
const { conf, messages, prompt } = get(
component,
['obj', 'params', 'infor'],
{},
);
const { replacedOutput } = useReplaceIdWithText(output);
const stopPropagation: MouseEventHandler = useCallback((e) => {
e.stopPropagation();
@@ -49,6 +70,13 @@ export function NextNodePopover({ children, nodeId, name }: IProps) {

const getLabel = useGetComponentLabelByValue(nodeId);

const [inputPage, setInputPage] = useState(1);
const pageSize = 3;
const pagedInputs = inputs.slice(
(inputPage - 1) * pageSize,
inputPage * pageSize,
);

return (
<Popover>
<PopoverTrigger onClick={stopPropagation} asChild>
@@ -59,62 +87,226 @@ export function NextNodePopover({ children, nodeId, name }: IProps) {
side={'right'}
sideOffset={20}
onClick={stopPropagation}
className="w-[400px]"
className="w-[800px] p-4"
style={{ maxHeight: 600, overflow: 'auto' }}
>
<div className="mb-3 font-semibold text-[16px]">
{name} {t('operationResults')}
</div>
<div className="flex w-full gap-4 flex-col">
<div className="flex flex-col space-y-1.5">
<span className="font-semibold text-[14px]">{t('input')}</span>
<div
style={
theme === 'dark'
? {
backgroundColor: 'rgba(150, 150, 150, 0.2)',
}
: {}
}
className={`bg-gray-100 p-1 rounded`}
>
<Table>
<TableHeader>
<TableRow>
<TableHead>{t('componentId')}</TableHead>
<TableHead className="w-[60px]">{t('content')}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{inputs.map((x, idx) => (
<TableRow key={idx}>
<TableCell>{getLabel(x.component_id)}</TableCell>
<TableCell className="truncate">{x.content}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
<div className="flex flex-col space-y-1.5">
<span className="font-semibold text-[14px]">{t('output')}</span>
<div
style={
theme === 'dark'
? {
backgroundColor: 'rgba(150, 150, 150, 0.2)',
}
: {}
}
className="bg-gray-100 p-1 rounded"
>
<JsonView
src={replacedOutput}
displaySize={30}
className="w-full max-h-[300px] break-words overflow-auto"
/>
</div>
</div>
</div>
<Card
bordered={false}
style={{ marginBottom: 16, padding: 0 }}
bodyStyle={{ padding: 0 }}
>
<Typography.Title
level={5}
style={{
marginBottom: 16,
fontWeight: 600,
fontSize: 18,
borderBottom: '1px solid #f0f0f0',
paddingBottom: 8,
}}
>
{name} {t('operationResults')}
</Typography.Title>
</Card>
<Tabs
defaultActiveKey="input"
items={[
{
key: 'input',
label: t('input'),
children: (
<Card
size="small"
className="bg-gray-50 dark:bg-gray-800"
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
bodyStyle={{ padding: 16 }}
>
<Table>
<TableHeader>
<TableRow>
<TableHead>{t('componentId')}</TableHead>
<TableHead className="w-[60px]">
{t('content')}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{pagedInputs.map((x, idx) => (
<TableRow key={idx + (inputPage - 1) * pageSize}>
<TableCell>{getLabel(x.component_id)}</TableCell>
<TableCell className="truncate">
{x.content}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{/* Pagination */}
{inputs.length > pageSize && (
<Row justify="end" style={{ marginTop: 8 }}>
<Space>
<Button
size="small"
disabled={inputPage === 1}
onClick={() => setInputPage(inputPage - 1)}
>
Prev
</Button>
<span className="mx-2 text-sm">
{inputPage} / {Math.ceil(inputs.length / pageSize)}
</span>
<Button
size="small"
disabled={
inputPage === Math.ceil(inputs.length / pageSize)
}
onClick={() => setInputPage(inputPage + 1)}
>
Next
</Button>
</Space>
</Row>
)}
</Card>
),
},
{
key: 'output',
label: t('output'),
children: (
<Card
size="small"
className="bg-gray-50 dark:bg-gray-800"
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
bodyStyle={{ padding: 16 }}
>
<JsonView
src={replacedOutput}
displaySize={30}
className="w-full max-h-[300px] break-words overflow-auto"
/>
</Card>
),
},
{
key: 'infor',
label: t('infor'),
children: (
<Card
size="small"
className="bg-gray-50 dark:bg-gray-800"
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
bodyStyle={{ padding: 16 }}
>
<Row gutter={16}>
<Col span={12}>
{conf && (
<Card
size="small"
bordered={false}
style={{
marginBottom: 16,
background: 'transparent',
}}
bodyStyle={{ padding: 0 }}
>
<Typography.Text
strong
style={{
color: '#888',
marginBottom: 8,
display: 'block',
}}
>
Configuration:
</Typography.Text>
<JsonView
src={conf}
displaySize={30}
className="w-full max-h-[120px] break-words overflow-auto"
/>
</Card>
)}
{prompt && (
<Card
size="small"
bordered={false}
style={{ background: 'transparent' }}
bodyStyle={{ padding: 0 }}
>
<Row
align="middle"
justify="space-between"
style={{ marginBottom: 8 }}
>
<Col>
<Typography.Text strong style={{ color: '#888' }}>
Prompt:
</Typography.Text>
</Col>
<Col>
<Button
size="small"
onClick={() => {
const inlineString = prompt
.replace(/\s+/g, ' ')
.trim();
navigator.clipboard.writeText(inlineString);
message.success(
'Prompt copied as single line!',
);
}}
>
Copy as single line
</Button>
</Col>
</Row>
<Input.TextArea
value={prompt}
readOnly
autoSize={{ minRows: 2, maxRows: 6 }}
className="bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700"
/>
</Card>
)}
</Col>
<Col span={12}>
{messages && (
<Card
size="small"
bordered={false}
style={{
marginBottom: 16,
background: 'transparent',
}}
bodyStyle={{ padding: 0 }}
>
<Typography.Text
strong
style={{
color: '#888',
marginBottom: 8,
display: 'block',
}}
>
Messages:
</Typography.Text>
<div className="max-h-[300px] overflow-auto">
<JsonView
src={messages}
displaySize={30}
className="w-full break-words"
/>
</div>
</Card>
)}
</Col>
</Row>
</Card>
),
},
]}
/>
</PopoverContent>
</Popover>
);

正在加载...
取消
保存