mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-07 11:02:12 +02:00
feat: support {{variable}} in HTTP tool URL and add variable extraction timing option
- Apply render_template() to the endpoint URL at runtime so
{{initial_context.*}} and {{gathered_context.*}} work in URL
paths (same logic as node prompts and preset parameters).
- Update URL validation to allow template variables in the path
but reject them in the domain.
- Add variable_extraction_timing setting (before/after/both) to
HTTP tools, controlling when conversation variable extraction
runs relative to the tool call.
- Add Variable Extraction Timing radio group to the tool Settings
tab with Before/After/Both options.
This commit is contained in:
parent
49fcb770a4
commit
66bc5d3e60
4 changed files with 95 additions and 0 deletions
|
|
@ -378,6 +378,14 @@ class CustomToolManager:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Determine when to run variable extraction relative to the tool call
|
||||||
|
extraction_timing = config.get("variable_extraction_timing", "before")
|
||||||
|
|
||||||
|
if extraction_timing in ("before", "both"):
|
||||||
|
await self._engine._perform_variable_extraction_if_needed(
|
||||||
|
self._engine._current_node, run_in_background=False
|
||||||
|
)
|
||||||
|
|
||||||
result = await execute_http_tool(
|
result = await execute_http_tool(
|
||||||
tool=tool,
|
tool=tool,
|
||||||
arguments=function_call_params.arguments,
|
arguments=function_call_params.arguments,
|
||||||
|
|
@ -386,6 +394,11 @@ class CustomToolManager:
|
||||||
organization_id=await self.get_organization_id(),
|
organization_id=await self.get_organization_id(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if extraction_timing in ("after", "both"):
|
||||||
|
await self._engine._perform_variable_extraction_if_needed(
|
||||||
|
self._engine._current_node, run_in_background=False
|
||||||
|
)
|
||||||
|
|
||||||
await function_call_params.result_callback(result)
|
await function_call_params.result_callback(result)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { AlertCircle, Variable } from "lucide-react";
|
||||||
|
|
||||||
import type { RecordingResponseSchema } from "@/client/types.gen";
|
import type { RecordingResponseSchema } from "@/client/types.gen";
|
||||||
import { StaticTextWarning, TextOrAudioInput } from "@/components/flow/TextOrAudioInput";
|
import { StaticTextWarning, TextOrAudioInput } from "@/components/flow/TextOrAudioInput";
|
||||||
import {
|
import {
|
||||||
|
|
@ -17,6 +19,7 @@ import {
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
|
|
@ -45,6 +48,8 @@ export interface HttpApiToolConfigProps {
|
||||||
onCustomMessageTypeChange: (type: 'text' | 'audio') => void;
|
onCustomMessageTypeChange: (type: 'text' | 'audio') => void;
|
||||||
customMessageRecordingId: string;
|
customMessageRecordingId: string;
|
||||||
onCustomMessageRecordingIdChange: (id: string) => void;
|
onCustomMessageRecordingIdChange: (id: string) => void;
|
||||||
|
variableExtractionTiming: 'before' | 'after' | 'both';
|
||||||
|
onVariableExtractionTimingChange: (timing: 'before' | 'after' | 'both') => void;
|
||||||
recordings?: RecordingResponseSchema[];
|
recordings?: RecordingResponseSchema[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,6 +78,8 @@ export function HttpApiToolConfig({
|
||||||
onCustomMessageTypeChange,
|
onCustomMessageTypeChange,
|
||||||
customMessageRecordingId,
|
customMessageRecordingId,
|
||||||
onCustomMessageRecordingIdChange,
|
onCustomMessageRecordingIdChange,
|
||||||
|
variableExtractionTiming,
|
||||||
|
onVariableExtractionTimingChange,
|
||||||
recordings = [],
|
recordings = [],
|
||||||
}: HttpApiToolConfigProps) {
|
}: HttpApiToolConfigProps) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -152,6 +159,63 @@ export function HttpApiToolConfig({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 pt-4 border-t">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Variable className="h-4 w-4" />
|
||||||
|
<Label>Variable Extraction Timing</Label>
|
||||||
|
</div>
|
||||||
|
<Label className="text-xs text-muted-foreground">
|
||||||
|
Control when conversation variables are extracted for use in tool parameters
|
||||||
|
</Label>
|
||||||
|
<RadioGroup
|
||||||
|
value={variableExtractionTiming}
|
||||||
|
onValueChange={(v) => onVariableExtractionTimingChange(v as 'before' | 'after' | 'both')}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
htmlFor="extract-before"
|
||||||
|
className={`flex items-start gap-3 p-3 border rounded-lg cursor-pointer transition-colors ${
|
||||||
|
variableExtractionTiming === 'before' ? 'border-primary bg-primary/5' : 'hover:bg-muted/50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<RadioGroupItem value="before" id="extract-before" className="mt-0.5" />
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-sm">Before tool call</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Extract variables from the conversation before the HTTP request. Use when URL or preset parameters depend on gathered context.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
htmlFor="extract-after"
|
||||||
|
className={`flex items-start gap-3 p-3 border rounded-lg cursor-pointer transition-colors ${
|
||||||
|
variableExtractionTiming === 'after' ? 'border-primary bg-primary/5' : 'hover:bg-muted/50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<RadioGroupItem value="after" id="extract-after" className="mt-0.5" />
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-sm">After tool call</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Extract variables after the HTTP response returns. Use when downstream nodes need data derived from the response.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
htmlFor="extract-both"
|
||||||
|
className={`flex items-start gap-3 p-3 border rounded-lg cursor-pointer transition-colors ${
|
||||||
|
variableExtractionTiming === 'both' ? 'border-primary bg-primary/5' : 'hover:bg-muted/50'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<RadioGroupItem value="both" id="extract-both" className="mt-0.5" />
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-sm">Both</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
Extract before and after the tool call. Ensures parameters use the latest context and the response is captured for downstream use.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-2 pt-4 border-t">
|
<div className="grid gap-2 pt-4 border-t">
|
||||||
<Label>Custom Message</Label>
|
<Label>Custom Message</Label>
|
||||||
<Label className="text-xs text-muted-foreground">
|
<Label className="text-xs text-muted-foreground">
|
||||||
|
|
@ -166,6 +230,11 @@ export function HttpApiToolConfig({
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<StaticTextWarning />
|
<StaticTextWarning />
|
||||||
|
<div className="flex items-start gap-2 rounded-md bg-amber-50 p-2 text-xs text-amber-700 border border-amber-200">
|
||||||
|
<AlertCircle className="h-3.5 w-3.5 mt-0.5 shrink-0" />
|
||||||
|
<span>This text is spoken as-is. For multilingual workflows, choose your phrasing carefully.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
value={customMessage}
|
value={customMessage}
|
||||||
onChange={(e) => onCustomMessageChange(e.target.value)}
|
onChange={(e) => onCustomMessageChange(e.target.value)}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ export default function ToolDetailPage() {
|
||||||
// HTTP API form state - custom message type
|
// HTTP API form state - custom message type
|
||||||
const [customMessageType, setCustomMessageType] = useState<'text' | 'audio'>('text');
|
const [customMessageType, setCustomMessageType] = useState<'text' | 'audio'>('text');
|
||||||
const [customMessageRecordingId, setCustomMessageRecordingId] = useState("");
|
const [customMessageRecordingId, setCustomMessageRecordingId] = useState("");
|
||||||
|
const [variableExtractionTiming, setVariableExtractionTiming] = useState<'before' | 'after' | 'both'>('before');
|
||||||
|
|
||||||
// MCP form state
|
// MCP form state
|
||||||
const [mcpUrl, setMcpUrl] = useState("");
|
const [mcpUrl, setMcpUrl] = useState("");
|
||||||
|
|
@ -225,6 +226,9 @@ export default function ToolDetailPage() {
|
||||||
setCustomMessage(config.customMessage || "");
|
setCustomMessage(config.customMessage || "");
|
||||||
setCustomMessageType(config.customMessageType || "text");
|
setCustomMessageType(config.customMessageType || "text");
|
||||||
setCustomMessageRecordingId(config.customMessageRecordingId || "");
|
setCustomMessageRecordingId(config.customMessageRecordingId || "");
|
||||||
|
setVariableExtractionTiming(
|
||||||
|
(config.variable_extraction_timing as 'before' | 'after' | 'both') || 'before'
|
||||||
|
);
|
||||||
|
|
||||||
// Convert headers object to array
|
// Convert headers object to array
|
||||||
if (config.headers) {
|
if (config.headers) {
|
||||||
|
|
@ -437,6 +441,7 @@ export default function ToolDetailPage() {
|
||||||
customMessage: customMessageType === 'text' ? (customMessage || undefined) : undefined,
|
customMessage: customMessageType === 'text' ? (customMessage || undefined) : undefined,
|
||||||
customMessageType,
|
customMessageType,
|
||||||
customMessageRecordingId: customMessageType === 'audio' ? (customMessageRecordingId || undefined) : undefined,
|
customMessageRecordingId: customMessageType === 'audio' ? (customMessageRecordingId || undefined) : undefined,
|
||||||
|
variable_extraction_timing: variableExtractionTiming,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -762,6 +767,8 @@ const data = await response.json();`;
|
||||||
onCustomMessageTypeChange={setCustomMessageType}
|
onCustomMessageTypeChange={setCustomMessageType}
|
||||||
customMessageRecordingId={customMessageRecordingId}
|
customMessageRecordingId={customMessageRecordingId}
|
||||||
onCustomMessageRecordingIdChange={setCustomMessageRecordingId}
|
onCustomMessageRecordingIdChange={setCustomMessageRecordingId}
|
||||||
|
variableExtractionTiming={variableExtractionTiming}
|
||||||
|
onVariableExtractionTimingChange={setVariableExtractionTiming}
|
||||||
recordings={recordings}
|
recordings={recordings}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1894,6 +1894,12 @@ export type HttpApiConfig = {
|
||||||
* Recording ID for an audio custom message.
|
* Recording ID for an audio custom message.
|
||||||
*/
|
*/
|
||||||
customMessageRecordingId?: string | null;
|
customMessageRecordingId?: string | null;
|
||||||
|
/**
|
||||||
|
* VariableExtractionTiming
|
||||||
|
*
|
||||||
|
* When to run variable extraction relative to the tool call (before, after, or both)
|
||||||
|
*/
|
||||||
|
variable_extraction_timing?: 'before' | 'after' | 'both' | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue