ソースを参照

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 ファイルの表示

# Exclude hash-like temporary files like 9b5ad71b2ce5302211f9c61530b329a4922fc6a4 # 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]* *[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/ .lh/
.venv
docker/data

+ 4
- 2
agent/canvas.py ファイルの表示

import json import json
from copy import deepcopy from copy import deepcopy
from functools import partial from functools import partial

import pandas as pd import pandas as pd


from agent.component import component_class from agent.component import component_class
return self.components["begin"]["obj"]._param.query return self.components["begin"]["obj"]._param.query


def get_component_input_elements(self, cpnnm): 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 ファイルの表示

class ComponentParamBase(ABC): class ComponentParamBase(ABC):
def __init__(self): def __init__(self):
self.output_var_name = "output" self.output_var_name = "output"
self.infor_var_name = "infor"
self.message_history_window_size = 22 self.message_history_window_size = 22
self.query = [] self.query = []
self.inputs = [] self.inputs = []
def set_output(self, v): def set_output(self, v):
setattr(self._param, self._param.output_var_name, 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]: def _fetch_outputs_from(self, sources: list[dict[str, Any]]) -> list[pd.DataFrame]:
outs = [] outs = []
for q in sources: for q in sources:
elif q.get("value"): elif q.get("value"):
outs.append(pd.DataFrame([{"content": q["value"]}])) outs.append(pd.DataFrame([{"content": q["value"]}]))
return outs return outs

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

+ 2
- 0
agent/component/categorize.py ファイルの表示

input = self.get_input() input = self.get_input()
input = " - ".join(input["content"]) if "content" in input else "" input = " - ".join(input["content"]) if "content" in input else ""
chat_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.CHAT, self._param.llm_id) 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: "}], ans = chat_mdl.chat(self._param.get_prompt(input), [{"role": "user", "content": "\nCategory: "}],
self._param.gen_conf()) self._param.gen_conf())
logging.debug(f"input: {input}, answer: {str(ans)}") logging.debug(f"input: {input}, answer: {str(ans)}")

+ 2
- 2
agent/component/generate.py ファイルの表示

msg.append({"role": "user", "content": "Output: "}) msg.append({"role": "user", "content": "Output: "})
ans = chat_mdl.chat(msg[0]["content"], msg[1:], self._param.gen_conf()) ans = chat_mdl.chat(msg[0]["content"], msg[1:], self._param.gen_conf())
ans = re.sub(r"^.*</think>", "", ans, flags=re.DOTALL) 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: if self._param.cite and "chunks" in retrieval_res.columns:
res = self.set_cite(retrieval_res, ans) res = self.set_cite(retrieval_res, ans)
return pd.DataFrame([res]) return pd.DataFrame([res])
if self._param.cite and "chunks" in retrieval_res.columns: if self._param.cite and "chunks" in retrieval_res.columns:
res = self.set_cite(retrieval_res, answer) res = self.set_cite(retrieval_res, answer)
yield res 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)) self.set_output(Generate.be_output(res))


def debug(self, **kwargs): def debug(self, **kwargs):

+ 2
- 0
agent/component/keyword.py ファイルの表示





chat_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.CHAT, self._param.llm_id) 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}], ans = chat_mdl.chat(self._param.get_prompt(), [{"role": "user", "content": query}],
self._param.gen_conf()) self._param.gen_conf())



+ 0
- 2
api/apps/canvas_app.py ファイルの表示

from agent.canvas import Canvas from agent.canvas import Canvas
from peewee import MySQLDatabase, PostgresqlDatabase from peewee import MySQLDatabase, PostgresqlDatabase
from api.db.db_models import APIToken from api.db.db_models import APIToken
import logging
import time import time


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

+ 1
- 0
web/src/locales/en.ts ファイルの表示

promptTip: 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.', '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', promptMessage: 'Prompt is required',
infor: 'Information run',
knowledgeBasesTip: knowledgeBasesTip:
'Select the knowledge bases to associate with this chat assistant, or choose variables containing knowledge base IDs below.', 'Select the knowledge bases to associate with this chat assistant, or choose variables containing knowledge base IDs below.',
knowledgeBaseVars: 'Knowledge base variables', knowledgeBaseVars: 'Knowledge base variables',

+ 250
- 58
web/src/pages/flow/canvas/node/popover.tsx ファイルの表示

import { useFetchFlow } from '@/hooks/flow-hooks'; import { useFetchFlow } from '@/hooks/flow-hooks';
import get from 'lodash/get'; 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 JsonView from 'react18-json-view';
import 'react18-json-view/src/style.css'; import 'react18-json-view/src/style.css';
import { useReplaceIdWithText } from '../../hooks'; import { useReplaceIdWithText } from '../../hooks';


import { useTheme } from '@/components/theme-provider';
import { import {
Popover, Popover,
PopoverContent, PopoverContent,
TableRow, TableRow,
} from '@/components/ui/table'; } from '@/components/ui/table';
import { useTranslate } from '@/hooks/common-hooks'; 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'; import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';


interface IProps extends React.PropsWithChildren { interface IProps extends React.PropsWithChildren {
const { t } = useTranslate('flow'); const { t } = useTranslate('flow');


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

const component = useMemo(() => { const component = useMemo(() => {
return get(data, ['dsl', 'components', nodeId], {}); return get(data, ['dsl', 'components', nodeId], {});
}, [nodeId, data]); }, [nodeId, data]);
[], [],
); );
const output = get(component, ['obj', 'output'], {}); const output = get(component, ['obj', 'output'], {});
const { conf, messages, prompt } = get(
component,
['obj', 'params', 'infor'],
{},
);
const { replacedOutput } = useReplaceIdWithText(output); const { replacedOutput } = useReplaceIdWithText(output);
const stopPropagation: MouseEventHandler = useCallback((e) => { const stopPropagation: MouseEventHandler = useCallback((e) => {
e.stopPropagation(); e.stopPropagation();


const getLabel = useGetComponentLabelByValue(nodeId); const getLabel = useGetComponentLabelByValue(nodeId);


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

return ( return (
<Popover> <Popover>
<PopoverTrigger onClick={stopPropagation} asChild> <PopoverTrigger onClick={stopPropagation} asChild>
side={'right'} side={'right'}
sideOffset={20} sideOffset={20}
onClick={stopPropagation} 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> </PopoverContent>
</Popover> </Popover>
); );

読み込み中…
キャンセル
保存