mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 12:32:30 +08:00
Feat: HTTP componant supports variables (#10432)
### What problem does this PR solve? HTTP component supports variables. #10382   ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -19,11 +19,12 @@ import os
|
||||
import re
|
||||
import time
|
||||
from abc import ABC
|
||||
|
||||
import requests
|
||||
|
||||
from agent.component.base import ComponentBase, ComponentParamBase
|
||||
from api.utils.api_utils import timeout
|
||||
from deepdoc.parser import HtmlParser
|
||||
from agent.component.base import ComponentBase, ComponentParamBase
|
||||
|
||||
|
||||
class InvokeParam(ComponentParamBase):
|
||||
@ -43,11 +44,11 @@ class InvokeParam(ComponentParamBase):
|
||||
self.datatype = "json" # New parameter to determine data posting type
|
||||
|
||||
def check(self):
|
||||
self.check_valid_value(self.method.lower(), "Type of content from the crawler", ['get', 'post', 'put'])
|
||||
self.check_valid_value(self.method.lower(), "Type of content from the crawler", ["get", "post", "put"])
|
||||
self.check_empty(self.url, "End point URL")
|
||||
self.check_positive_integer(self.timeout, "Timeout time in second")
|
||||
self.check_boolean(self.clean_html, "Clean HTML")
|
||||
self.check_valid_value(self.datatype.lower(), "Data post type", ['json', 'formdata']) # Check for valid datapost value
|
||||
self.check_valid_value(self.datatype.lower(), "Data post type", ["json", "formdata"]) # Check for valid datapost value
|
||||
|
||||
|
||||
class Invoke(ComponentBase, ABC):
|
||||
@ -63,6 +64,18 @@ class Invoke(ComponentBase, ABC):
|
||||
args[para["key"]] = self._canvas.get_variable_value(para["ref"])
|
||||
|
||||
url = self._param.url.strip()
|
||||
|
||||
def replace_variable(match):
|
||||
var_name = match.group(1)
|
||||
try:
|
||||
value = self._canvas.get_variable_value(var_name)
|
||||
return str(value or "")
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
# {base_url} or {component_id@variable_name}
|
||||
url = re.sub(r"\{([a-zA-Z_][a-zA-Z0-9_.@-]*)\}", replace_variable, url)
|
||||
|
||||
if url.find("http") != 0:
|
||||
url = "http://" + url
|
||||
|
||||
@ -75,52 +88,32 @@ class Invoke(ComponentBase, ABC):
|
||||
proxies = {"http": self._param.proxy, "https": self._param.proxy}
|
||||
|
||||
last_e = ""
|
||||
for _ in range(self._param.max_retries+1):
|
||||
for _ in range(self._param.max_retries + 1):
|
||||
try:
|
||||
if method == 'get':
|
||||
response = requests.get(url=url,
|
||||
params=args,
|
||||
headers=headers,
|
||||
proxies=proxies,
|
||||
timeout=self._param.timeout)
|
||||
if method == "get":
|
||||
response = requests.get(url=url, params=args, headers=headers, proxies=proxies, timeout=self._param.timeout)
|
||||
if self._param.clean_html:
|
||||
sections = HtmlParser()(None, response.content)
|
||||
self.set_output("result", "\n".join(sections))
|
||||
else:
|
||||
self.set_output("result", response.text)
|
||||
|
||||
if method == 'put':
|
||||
if self._param.datatype.lower() == 'json':
|
||||
response = requests.put(url=url,
|
||||
json=args,
|
||||
headers=headers,
|
||||
proxies=proxies,
|
||||
timeout=self._param.timeout)
|
||||
if method == "put":
|
||||
if self._param.datatype.lower() == "json":
|
||||
response = requests.put(url=url, json=args, headers=headers, proxies=proxies, timeout=self._param.timeout)
|
||||
else:
|
||||
response = requests.put(url=url,
|
||||
data=args,
|
||||
headers=headers,
|
||||
proxies=proxies,
|
||||
timeout=self._param.timeout)
|
||||
response = requests.put(url=url, data=args, headers=headers, proxies=proxies, timeout=self._param.timeout)
|
||||
if self._param.clean_html:
|
||||
sections = HtmlParser()(None, response.content)
|
||||
self.set_output("result", "\n".join(sections))
|
||||
else:
|
||||
self.set_output("result", response.text)
|
||||
|
||||
if method == 'post':
|
||||
if self._param.datatype.lower() == 'json':
|
||||
response = requests.post(url=url,
|
||||
json=args,
|
||||
headers=headers,
|
||||
proxies=proxies,
|
||||
timeout=self._param.timeout)
|
||||
if method == "post":
|
||||
if self._param.datatype.lower() == "json":
|
||||
response = requests.post(url=url, json=args, headers=headers, proxies=proxies, timeout=self._param.timeout)
|
||||
else:
|
||||
response = requests.post(url=url,
|
||||
data=args,
|
||||
headers=headers,
|
||||
proxies=proxies,
|
||||
timeout=self._param.timeout)
|
||||
response = requests.post(url=url, data=args, headers=headers, proxies=proxies, timeout=self._param.timeout)
|
||||
if self._param.clean_html:
|
||||
self.set_output("result", "\n".join(sections))
|
||||
else:
|
||||
|
||||
@ -1170,6 +1170,8 @@ export default {
|
||||
cleanHtml: 'HTML bereinigen',
|
||||
cleanHtmlTip:
|
||||
'Wenn die Antwort im HTML-Format vorliegt und nur der Hauptinhalt gewünscht wird, schalten Sie dies bitte ein.',
|
||||
invalidUrl:
|
||||
'Muss eine gültige URL oder eine URL mit Variablenplatzhaltern im Format {Variablenname} oder {Komponente@Variable} sein',
|
||||
reference: 'Referenz',
|
||||
input: 'Eingabe',
|
||||
output: 'Ausgabe',
|
||||
|
||||
@ -1397,6 +1397,8 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
cleanHtml: 'Clean HTML',
|
||||
cleanHtmlTip:
|
||||
'If the response is HTML formatted and only the primary content wanted, please toggle it on.',
|
||||
invalidUrl:
|
||||
'Must be a valid URL or URL with variable placeholders in the format {variable_name} or {component@variable}',
|
||||
reference: 'Reference',
|
||||
input: 'Input',
|
||||
output: 'Output',
|
||||
|
||||
@ -866,6 +866,19 @@ export default {
|
||||
noteDescription: 'Nota',
|
||||
notePlaceholder: 'Por favor ingresa una nota',
|
||||
runningHintText: 'está corriendo...🕞',
|
||||
|
||||
invoke: 'Solicitud HTTP',
|
||||
invokeDescription:
|
||||
'Un componente capaz de llamar a servicios remotos, utilizando las salidas de otros componentes o constantes como entradas.',
|
||||
url: 'Url',
|
||||
method: 'Método',
|
||||
timeout: 'Tiempo de espera',
|
||||
headers: 'Encabezados',
|
||||
cleanHtml: 'Limpiar HTML',
|
||||
cleanHtmlTip:
|
||||
'Si la respuesta está formateada en HTML y solo se desea el contenido principal, actívelo.',
|
||||
invalidUrl:
|
||||
'Debe ser una URL válida o una URL con marcadores de posición de variables en el formato {nombre_variable} o {componente@variable}',
|
||||
},
|
||||
footer: {
|
||||
profile: 'Todos los derechos reservados @ React',
|
||||
|
||||
@ -1096,6 +1096,8 @@ export default {
|
||||
cleanHtml: 'Nettoyer le HTML',
|
||||
cleanHtmlTip:
|
||||
'Si la réponse est au format HTML et que seul le contenu principal est souhaité, activez cette option.',
|
||||
invalidUrl:
|
||||
'Doit être une URL valide ou une URL avec des espaces réservés de variables au format {nom_variable} ou {composant@variable}',
|
||||
reference: 'Référence',
|
||||
input: 'Entrée',
|
||||
output: 'Sortie',
|
||||
|
||||
@ -1051,6 +1051,20 @@ export default {
|
||||
note: 'Catatan',
|
||||
noteDescription: 'Catatan',
|
||||
notePlaceholder: 'Silakan masukkan catatan',
|
||||
|
||||
invoke: 'Permintaan HTTP',
|
||||
invokeDescription:
|
||||
'Komponen yang mampu memanggil layanan remote, menggunakan output komponen lain atau konstanta sebagai input.',
|
||||
url: 'Url',
|
||||
method: 'Metode',
|
||||
timeout: 'Waktu habis',
|
||||
headers: 'Header',
|
||||
cleanHtml: 'Bersihkan HTML',
|
||||
cleanHtmlTip:
|
||||
'Jika respons diformat HTML dan hanya ingin konten utama, aktifkan opsi ini.',
|
||||
invalidUrl:
|
||||
'Harus berupa URL yang valid atau URL dengan placeholder variabel dalam format {nama_variabel} atau {komponen@variabel}',
|
||||
|
||||
prompt: 'Prompt',
|
||||
promptTip:
|
||||
'Gunakan prompt sistem untuk menjelaskan tugas untuk LLM, tentukan bagaimana harus merespons, dan menguraikan persyaratan lainnya. Prompt sistem sering digunakan bersama dengan kunci (variabel), yang berfungsi sebagai berbagai input data untuk LLM. Gunakan garis miring `/` atau tombol (x) untuk menampilkan kunci yang digunakan.',
|
||||
|
||||
@ -1098,6 +1098,8 @@ export default {
|
||||
cleanHtml: 'HTMLをクリーン',
|
||||
cleanHtmlTip:
|
||||
'応答がHTML形式であり、主要なコンテンツのみが必要な場合は、これをオンにしてください。',
|
||||
invalidUrl:
|
||||
'有効なURLまたは{variable_name}または{component@variable}形式の変数プレースホルダーを含むURLである必要があります',
|
||||
reference: '参照',
|
||||
input: '入力',
|
||||
output: '出力',
|
||||
|
||||
@ -1066,6 +1066,8 @@ export default {
|
||||
cleanHtml: 'Limpar HTML',
|
||||
cleanHtmlTip:
|
||||
'Se a resposta for formatada em HTML e apenas o conteúdo principal for desejado, ative esta opção.',
|
||||
invalidUrl:
|
||||
'Deve ser uma URL válida ou uma URL com marcadores de posição de variáveis no formato {nome_variável} ou {componente@variável}',
|
||||
|
||||
reference: 'Referência',
|
||||
input: 'Entrada',
|
||||
|
||||
@ -1327,6 +1327,8 @@ export default {
|
||||
cleanHtml: 'Очистить HTML',
|
||||
cleanHtmlTip:
|
||||
'Включите, если нужен только основной контент из HTML-ответа.',
|
||||
invalidUrl:
|
||||
'Должен быть действительный URL или URL с заполнителями переменных в формате {имя_переменной} или {компонент@переменная}',
|
||||
reference: 'Ссылка',
|
||||
input: 'Вход',
|
||||
output: 'Выход',
|
||||
|
||||
@ -1128,6 +1128,8 @@ export default {
|
||||
cleanHtml: 'Làm sạch HTML',
|
||||
cleanHtmlTip:
|
||||
'Nếu phản hồi được định dạng HTML và chỉ muốn nội dung chính, hãy bật nó lên.',
|
||||
invalidUrl:
|
||||
'Phải là URL hợp lệ hoặc URL có chứa các biến theo định dạng {ten_bien} hoặc {thanh_phan@bien}',
|
||||
reference: 'Tham khảo',
|
||||
input: 'Đầu vào',
|
||||
output: 'Đầu ra',
|
||||
|
||||
@ -1153,6 +1153,8 @@ export default {
|
||||
headers: '請求頭',
|
||||
cleanHtml: '清除 HTML',
|
||||
cleanHtmlTip: '如果回應是 HTML 格式並且只需要主要內容,請將其開啟。',
|
||||
invalidUrl:
|
||||
'必須是有效的 URL 或包含變量佔位符的 URL,格式為 {variable_name} 或 {component@variable}',
|
||||
reference: '引用',
|
||||
input: '輸入',
|
||||
output: '輸出',
|
||||
|
||||
@ -1359,6 +1359,8 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
headers: '请求头',
|
||||
cleanHtml: '清除 HTML',
|
||||
cleanHtmlTip: '如果响应是 HTML 格式且只需要主要内容,请将其打开。',
|
||||
invalidUrl:
|
||||
'必须是有效的 URL 或包含变量占位符的 URL,格式为 {variable_name} 或 {component@variable}',
|
||||
reference: '引用',
|
||||
input: '输入',
|
||||
output: '输出',
|
||||
|
||||
@ -26,6 +26,7 @@ import { INextOperatorForm } from '../../interface';
|
||||
import { buildOutputList } from '../../utils/build-output-list';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { Output } from '../components/output';
|
||||
import { PromptEditor } from '../components/prompt-editor';
|
||||
import { FormSchema, FormSchemaType } from './schema';
|
||||
import { useEditVariableRecord } from './use-edit-variable';
|
||||
import { VariableDialog } from './variable-dialog';
|
||||
@ -98,7 +99,13 @@ function InvokeForm({ node }: INextOperatorForm) {
|
||||
<FormItem>
|
||||
<FormLabel>{t('flow.url')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} placeholder="http://" />
|
||||
<PromptEditor
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder="http://"
|
||||
showToolbar={false}
|
||||
multiLine={false}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@ -6,8 +6,54 @@ export const VariableFormSchema = z.object({
|
||||
value: z.string(),
|
||||
});
|
||||
|
||||
// {user_id} or {component@variable}
|
||||
const placeholderRegex = /\{([a-zA-Z_][a-zA-Z0-9_.@-]*)\}/g;
|
||||
|
||||
// URL validation schema that accepts:
|
||||
// 1. Standard URLs (e.g. https://example.com/api)
|
||||
// 2. URLs with variable placeholders in curly braces (e.g. https://api/{user_id}/posts)
|
||||
const urlValidation = z.string().refine(
|
||||
(val) => {
|
||||
if (!val) return false;
|
||||
|
||||
const hasPlaceholders = val.includes('{') && val.includes('}');
|
||||
const matches = [...val.matchAll(placeholderRegex)];
|
||||
|
||||
if (hasPlaceholders) {
|
||||
if (
|
||||
!matches.length ||
|
||||
matches.some((m) => !/^[a-zA-Z_][a-zA-Z0-9_.@-]*$/.test(m[1]))
|
||||
)
|
||||
return false;
|
||||
|
||||
if ((val.match(/{/g) || []).length !== (val.match(/}/g) || []).length)
|
||||
return false;
|
||||
|
||||
const testURL = val.replace(placeholderRegex, 'placeholder');
|
||||
|
||||
return isValidURL(testURL);
|
||||
}
|
||||
|
||||
return isValidURL(val);
|
||||
},
|
||||
{
|
||||
message: 'Must be a valid URL or URL with variable placeholders',
|
||||
},
|
||||
);
|
||||
|
||||
function isValidURL(str: string): boolean {
|
||||
try {
|
||||
// Try to construct a full URL; prepend http:// if protocol is missing
|
||||
new URL(str.startsWith('http') ? str : `http://${str}`);
|
||||
return true;
|
||||
} catch {
|
||||
// Allow relative paths (e.g. /api/users) if needed
|
||||
return /^\/[a-zA-Z0-9]/.test(str);
|
||||
}
|
||||
}
|
||||
|
||||
export const FormSchema = z.object({
|
||||
url: z.string().url(),
|
||||
url: urlValidation,
|
||||
method: z.string(),
|
||||
timeout: z.number(),
|
||||
headers: z.string(),
|
||||
|
||||
Reference in New Issue
Block a user