feat: add call tags extraction in workflow

This commit is contained in:
Abhishek Kumar 2026-02-10 08:15:15 +05:30
parent 7a102026fb
commit 15809e03a4
8 changed files with 345 additions and 7 deletions

View file

@ -51,6 +51,8 @@ class NodeDataDTO(BaseModel):
extraction_enabled: bool = False
extraction_prompt: Optional[str] = None
extraction_variables: Optional[list[ExtractionVariableDTO]] = None
call_tags_enabled: bool = False
call_tags_prompt: Optional[str] = None
add_global_prompt: bool = True
wait_for_user_response: bool = False
wait_for_user_response_timeout: Optional[float] = None

View file

@ -207,8 +207,9 @@ class PipecatEngine:
)
logger.info(f"Arguments: {function_call_params.arguments}")
# Perform variable extraction before transitioning to new node
# Perform variable extraction and call tags extraction before transitioning to new node
await self._perform_variable_extraction_if_needed(self._current_node)
await self._perform_call_tags_extraction_if_needed(self._current_node)
# Set context for the new node, so that when the function call result
# frame is received by LLMContextAggregator and an LLM generation
@ -413,6 +414,54 @@ class PipecatEngine:
)
await _do_extraction()
async def _perform_call_tags_extraction_if_needed(
self, node: Optional[Node], run_in_background: bool = True
) -> None:
"""Perform call tags extraction if the node has it enabled.
Extracted tags are merged into gathered_context["call_tags"].
Args:
node: The node to extract call tags from.
run_in_background: If True, runs extraction as a fire-and-forget task.
If False, awaits the extraction synchronously.
"""
if not (node and node.call_tags_enabled):
return
parent_context = get_current_turn_context()
call_tags_prompt = self._format_prompt(node.call_tags_prompt or "")
async def _do_extraction():
try:
extracted_tags = (
await self._variable_extraction_manager._perform_call_tags_extraction(
parent_context, call_tags_prompt
)
)
# Merge into existing call_tags (no duplicates)
existing_tags = self._gathered_context.get("call_tags", [])
for tag in extracted_tags:
if tag not in existing_tags:
existing_tags.append(tag)
self._gathered_context["call_tags"] = existing_tags
logger.debug(
f"Call tags extraction completed. Tags: {existing_tags}"
)
except Exception as e:
logger.error(f"Error during call tags extraction: {str(e)}")
if run_in_background:
logger.debug(
f"Scheduling background call tags extraction for node: {node.name}"
)
asyncio.create_task(_do_extraction())
else:
logger.debug(
f"Performing synchronous call tags extraction for node: {node.name}"
)
await _do_extraction()
async def _setup_llm_context(self, node: Node) -> None:
"""Common method to set up LLM context"""
# Set node name for tracing
@ -527,10 +576,13 @@ class PipecatEngine:
# Mute the pipeline
self._mute_pipeline = True
# Perform final variable extraction synchronously before ending
# Perform final variable extraction and call tags extraction synchronously before ending
await self._perform_variable_extraction_if_needed(
self._current_node, run_in_background=False
)
await self._perform_call_tags_extraction_if_needed(
self._current_node, run_in_background=False
)
frame_to_push = CancelFrame() if abort_immediately else EndFrame()

View file

@ -119,8 +119,8 @@ class VariableExtractionManager:
conversation_history = "\n".join(conversation_lines)
system_prompt = (
"You are an assistant tasked with extracting structured data from the conversation. "
"Return ONLY a valid JSON object with the requested variables as top-level keys. Do not wrap the JSON in markdown." # noqa: E501
"You are an assistant tasked with extracting structured data from a conversation. "
"Return ONLY a valid JSON object with the requested variables as top-level keys. Do not wrap the JSON in markdown."
)
# Use provided extraction_prompt as system prompt, or default
system_prompt = (
@ -184,3 +184,134 @@ class VariableExtractionManager:
logger.debug(f"Extracted variables: {extracted}")
return extracted
async def _perform_call_tags_extraction(
self,
parent_ctx: Any,
call_tags_prompt: str = "",
) -> list[str]:
"""Run a chat completion to extract call tags from the conversation.
Returns a list of tag strings.
"""
# ------------------------------------------------------------------
# Build a normalized conversation history (reuses the same helper
# logic from variable extraction).
# ------------------------------------------------------------------
def _get_role_and_content(msg: Any) -> tuple[str | None, str | None]:
if isinstance(msg, dict):
role = msg.get("role")
content_field = msg.get("content")
if isinstance(content_field, str):
content = content_field
elif isinstance(content_field, list):
texts = [
segment.get("text", "")
for segment in content_field
if isinstance(segment, dict) and segment.get("type") == "text"
]
content = " ".join(texts) if texts else None
else:
content = None
return role, content
role_attr = getattr(msg, "role", None)
parts_attr = getattr(msg, "parts", None)
if role_attr is None or parts_attr is None:
return None, None
role = "assistant" if role_attr == "model" else role_attr
texts: list[str] = []
for part in parts_attr:
text_val = getattr(part, "text", None)
if text_val:
texts.append(text_val)
content = " ".join(texts) if texts else None
return role, content
conversation_lines: list[str] = []
for msg in self._context.messages:
role, content = _get_role_and_content(msg)
if role in ("assistant", "user") and content:
conversation_lines.append(f"{role}: {content}")
conversation_history = "\n".join(conversation_lines)
system_prompt = (
"You are an assistant tasked with extracting call tags from a conversation. "
"Return ONLY a valid JSON array of short tag strings. Do not wrap the JSON in markdown. "
"Example: [\"interested\", \"follow_up_needed\", \"pricing_discussed\"]"
)
if call_tags_prompt:
system_prompt = system_prompt + "\n\n" + call_tags_prompt
user_prompt = (
"\n\nExtract relevant call tags from the following conversation:\n"
f"{conversation_history}"
)
extraction_context = LLMContext()
extraction_messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
]
extraction_context.set_messages(extraction_messages)
llm_response = await self._engine.llm.run_inference(extraction_context)
model_name = getattr(self._engine.llm, "model_name", "unknown")
if is_tracing_enabled():
tracer = trace.get_tracer("pipecat")
with tracer.start_as_current_span(
"llm-call-tags-extraction", context=parent_ctx
) as span:
add_llm_span_attributes(
span,
service_name=self._engine.llm.__class__.__name__,
model=model_name,
operation_name="llm-call-tags-extraction",
messages=extraction_messages,
output=llm_response,
stream=False,
parameters={},
)
if llm_response is None:
logger.warning("Call tags extractor returned no response; returning empty list.")
return []
parsed = parse_llm_json(llm_response)
# parse_llm_json returns a dict. If the LLM returned a JSON array,
# it will be stored under the "raw" key or similar. We need to
# handle both cases: a plain list from the LLM or a dict wrapper.
if isinstance(parsed, list):
tags = [str(t) for t in parsed if t]
elif isinstance(parsed, dict):
# If parse_llm_json wrapped a list in {"raw": ...}, try to
# extract the list. Otherwise flatten dict values.
import json as _json
raw = parsed.get("raw")
if raw and isinstance(raw, str):
try:
maybe_list = _json.loads(raw)
if isinstance(maybe_list, list):
tags = [str(t) for t in maybe_list if t]
else:
tags = []
except _json.JSONDecodeError:
tags = []
else:
# Flatten any list values in the dict
tags = []
for v in parsed.values():
if isinstance(v, list):
tags.extend(str(t) for t in v if t)
elif isinstance(v, str) and v:
tags.append(v)
else:
tags = []
logger.debug(f"Extracted call tags: {tags}")
return tags

View file

@ -43,6 +43,8 @@ class Node:
self.extraction_enabled = data.extraction_enabled
self.extraction_prompt = data.extraction_prompt
self.extraction_variables = data.extraction_variables
self.call_tags_enabled = data.call_tags_enabled
self.call_tags_prompt = data.call_tags_prompt
self.add_global_prompt = data.add_global_prompt
self.detect_voicemail = data.detect_voicemail
self.delayed_start = data.delayed_start

View file

@ -33,6 +33,10 @@ interface AgentNodeEditFormProps {
setExtractionPrompt: (value: string) => void;
variables: ExtractionVariable[];
setVariables: (vars: ExtractionVariable[]) => void;
callTagsEnabled: boolean;
setCallTagsEnabled: (value: boolean) => void;
callTagsPrompt: string;
setCallTagsPrompt: (value: string) => void;
addGlobalPrompt: boolean;
setAddGlobalPrompt: (value: boolean) => void;
toolUuids: string[];
@ -60,6 +64,9 @@ export const AgentNode = memo(({ data, selected, id }: AgentNodeProps) => {
const [extractionEnabled, setExtractionEnabled] = useState(data.extraction_enabled ?? false);
const [extractionPrompt, setExtractionPrompt] = useState(data.extraction_prompt ?? "");
const [variables, setVariables] = useState<ExtractionVariable[]>(data.extraction_variables ?? []);
// Call Tags state
const [callTagsEnabled, setCallTagsEnabled] = useState(data.call_tags_enabled ?? false);
const [callTagsPrompt, setCallTagsPrompt] = useState(data.call_tags_prompt ?? "");
const [addGlobalPrompt, setAddGlobalPrompt] = useState(data.add_global_prompt ?? true);
const [toolUuids, setToolUuids] = useState<string[]>(data.tool_uuids ?? []);
const [documentUuids, setDocumentUuids] = useState<string[]>(data.document_uuids ?? []);
@ -81,6 +88,8 @@ export const AgentNode = memo(({ data, selected, id }: AgentNodeProps) => {
extraction_enabled: extractionEnabled,
extraction_prompt: extractionPrompt,
extraction_variables: variables,
call_tags_enabled: callTagsEnabled,
call_tags_prompt: callTagsPrompt,
add_global_prompt: addGlobalPrompt,
tool_uuids: toolUuids.length > 0 ? toolUuids : undefined,
document_uuids: documentUuids.length > 0 ? documentUuids : undefined,
@ -101,6 +110,8 @@ export const AgentNode = memo(({ data, selected, id }: AgentNodeProps) => {
setExtractionEnabled(data.extraction_enabled ?? false);
setExtractionPrompt(data.extraction_prompt ?? "");
setVariables(data.extraction_variables ?? []);
setCallTagsEnabled(data.call_tags_enabled ?? false);
setCallTagsPrompt(data.call_tags_prompt ?? "");
setAddGlobalPrompt(data.add_global_prompt ?? true);
setToolUuids(data.tool_uuids ?? []);
setDocumentUuids(data.document_uuids ?? []);
@ -117,6 +128,8 @@ export const AgentNode = memo(({ data, selected, id }: AgentNodeProps) => {
setExtractionEnabled(data.extraction_enabled ?? false);
setExtractionPrompt(data.extraction_prompt ?? "");
setVariables(data.extraction_variables ?? []);
setCallTagsEnabled(data.call_tags_enabled ?? false);
setCallTagsPrompt(data.call_tags_prompt ?? "");
setAddGlobalPrompt(data.add_global_prompt ?? true);
setToolUuids(data.tool_uuids ?? []);
setDocumentUuids(data.document_uuids ?? []);
@ -219,6 +232,10 @@ export const AgentNode = memo(({ data, selected, id }: AgentNodeProps) => {
setExtractionPrompt={setExtractionPrompt}
variables={variables}
setVariables={setVariables}
callTagsEnabled={callTagsEnabled}
setCallTagsEnabled={setCallTagsEnabled}
callTagsPrompt={callTagsPrompt}
setCallTagsPrompt={setCallTagsPrompt}
addGlobalPrompt={addGlobalPrompt}
setAddGlobalPrompt={setAddGlobalPrompt}
toolUuids={toolUuids}
@ -247,6 +264,10 @@ const AgentNodeEditForm = ({
setExtractionPrompt,
variables,
setVariables,
callTagsEnabled,
setCallTagsEnabled,
callTagsPrompt,
setCallTagsPrompt,
addGlobalPrompt,
setAddGlobalPrompt,
toolUuids,
@ -340,13 +361,16 @@ const AgentNodeEditForm = ({
<div className="border rounded-md p-3 mt-2 space-y-2 bg-muted/20">
<Label>Extraction Prompt</Label>
<Label className="text-xs text-muted-foreground">
Provide an overall extraction prompt that guides how variables should be extracted from the conversation.
Provide an extraction prompt that guides how variables should be extracted from the conversation.
Example: You are given transcript of a conversation between a home owner and an insurance provider.
Extract the below variables.
</Label>
<Textarea
value={extractionPrompt}
onChange={(e) => setExtractionPrompt(e.target.value)}
className="min-h-[80px] max-h-[200px] resize-none"
style={{ overflowY: 'auto' }}
placeholder="Example: You are given transcript of a conversation between a home owner and an insurance provider. Extract the below variables."
/>
<Label>Variables</Label>
@ -390,6 +414,32 @@ const AgentNodeEditForm = ({
</div>
)}
{/* Call Tags Section */}
<div className="flex items-center space-x-2 pt-2">
<Switch id="enable-call-tags" checked={callTagsEnabled} onCheckedChange={setCallTagsEnabled} />
<Label htmlFor="enable-call-tags">Enable Call Tags Extraction</Label>
<Label className="text-xs text-muted-foreground ml-2">
Extract tags from the conversation at this step to categorize the call.
</Label>
</div>
{callTagsEnabled && (
<div className="border rounded-md p-3 mt-2 space-y-2 bg-muted/20">
<Label>Call Tags Prompt</Label>
<Label className="text-xs text-muted-foreground">
Provide a prompt that guides how call tags should be extracted from the conversation.
Example: Extract tags that describe the outcome and topics discussed in this call.
</Label>
<Textarea
value={callTagsPrompt}
onChange={(e) => setCallTagsPrompt(e.target.value)}
className="min-h-[80px] max-h-[200px] resize-none"
style={{ overflowY: 'auto' }}
placeholder="Example: Extract tags that describe the outcome and topics discussed in this call."
/>
</div>
)}
{/* Tools Section */}
<div className="pt-4 border-t mt-4">
<ToolSelector

View file

@ -26,6 +26,10 @@ interface EndCallEditFormProps {
setExtractionPrompt: (value: string) => void;
variables: ExtractionVariable[];
setVariables: (vars: ExtractionVariable[]) => void;
callTagsEnabled: boolean;
setCallTagsEnabled: (value: boolean) => void;
callTagsPrompt: string;
setCallTagsPrompt: (value: string) => void;
addGlobalPrompt: boolean;
setAddGlobalPrompt: (value: boolean) => void;
}
@ -49,6 +53,9 @@ export const EndCall = memo(({ data, selected, id }: EndCallNodeProps) => {
const [extractionEnabled, setExtractionEnabled] = useState(data.extraction_enabled ?? false);
const [extractionPrompt, setExtractionPrompt] = useState(data.extraction_prompt ?? "");
const [variables, setVariables] = useState<ExtractionVariable[]>(data.extraction_variables ?? []);
// Call Tags state
const [callTagsEnabled, setCallTagsEnabled] = useState(data.call_tags_enabled ?? false);
const [callTagsPrompt, setCallTagsPrompt] = useState(data.call_tags_prompt ?? "");
const [addGlobalPrompt, setAddGlobalPrompt] = useState(data.add_global_prompt ?? true);
// Compute if form has unsaved changes (simplified: only check prompt, name)
@ -68,6 +75,8 @@ export const EndCall = memo(({ data, selected, id }: EndCallNodeProps) => {
extraction_enabled: extractionEnabled,
extraction_prompt: extractionPrompt,
extraction_variables: variables,
call_tags_enabled: callTagsEnabled,
call_tags_prompt: callTagsPrompt,
add_global_prompt: addGlobalPrompt,
});
setOpen(false);
@ -85,6 +94,8 @@ export const EndCall = memo(({ data, selected, id }: EndCallNodeProps) => {
setExtractionEnabled(data.extraction_enabled ?? false);
setExtractionPrompt(data.extraction_prompt ?? "");
setVariables(data.extraction_variables ?? []);
setCallTagsEnabled(data.call_tags_enabled ?? false);
setCallTagsPrompt(data.call_tags_prompt ?? "");
setAddGlobalPrompt(data.add_global_prompt ?? true);
}
setOpen(newOpen);
@ -98,6 +109,8 @@ export const EndCall = memo(({ data, selected, id }: EndCallNodeProps) => {
setExtractionEnabled(data.extraction_enabled ?? false);
setExtractionPrompt(data.extraction_prompt ?? "");
setVariables(data.extraction_variables ?? []);
setCallTagsEnabled(data.call_tags_enabled ?? false);
setCallTagsPrompt(data.call_tags_prompt ?? "");
setAddGlobalPrompt(data.add_global_prompt ?? true);
}
}, [data, open]);
@ -153,6 +166,10 @@ export const EndCall = memo(({ data, selected, id }: EndCallNodeProps) => {
setExtractionPrompt={setExtractionPrompt}
variables={variables}
setVariables={setVariables}
callTagsEnabled={callTagsEnabled}
setCallTagsEnabled={setCallTagsEnabled}
callTagsPrompt={callTagsPrompt}
setCallTagsPrompt={setCallTagsPrompt}
addGlobalPrompt={addGlobalPrompt}
setAddGlobalPrompt={setAddGlobalPrompt}
/>
@ -173,6 +190,10 @@ const EndCallEditForm = ({
setExtractionPrompt,
variables,
setVariables,
callTagsEnabled,
setCallTagsEnabled,
callTagsPrompt,
setCallTagsPrompt,
addGlobalPrompt,
setAddGlobalPrompt,
}: EndCallEditFormProps) => {
@ -244,13 +265,16 @@ const EndCallEditForm = ({
<div className="border rounded-md p-3 mt-2 space-y-2 bg-muted/20">
<Label>Extraction Prompt</Label>
<Label className="text-xs text-muted-foreground">
Provide an overall extraction prompt that guides how variables should be extracted from the conversation.
Provide an extraction prompt that guides how variables should be extracted from the conversation.
Example: You are given transcript of a conversation between a home owner and an insurance provider.
Extract the below variables.
</Label>
<Textarea
value={extractionPrompt}
onChange={(e) => setExtractionPrompt(e.target.value)}
className="min-h-[80px] max-h-[200px] resize-none"
style={{ overflowY: 'auto' }}
placeholder="Example: You are given transcript of a conversation between a home owner and an insurance provider. Extract the below variables."
/>
<Label>Variables</Label>
@ -293,6 +317,32 @@ const EndCallEditForm = ({
</Button>
</div>
)}
{/* Call Tags Section */}
<div className="flex items-center space-x-2 pt-2">
<Switch id="enable-call-tags" checked={callTagsEnabled} onCheckedChange={setCallTagsEnabled} />
<Label htmlFor="enable-call-tags">Enable Call Tags Extraction</Label>
<Label className="text-xs text-muted-foreground ml-2">
Extract tags from the conversation at this step to categorize the call.
</Label>
</div>
{callTagsEnabled && (
<div className="border rounded-md p-3 mt-2 space-y-2 bg-muted/20">
<Label>Call Tags Prompt</Label>
<Label className="text-xs text-muted-foreground">
Provide a prompt that guides how call tags should be extracted from the conversation.
Example: Extract tags that describe the outcome and topics discussed in this call.
</Label>
<Textarea
value={callTagsPrompt}
onChange={(e) => setCallTagsPrompt(e.target.value)}
className="min-h-[80px] max-h-[200px] resize-none"
style={{ overflowY: 'auto' }}
placeholder="Example: Extract tags that describe the outcome and topics discussed in this call."
/>
</div>
)}
</div>
);
};

View file

@ -42,6 +42,10 @@ interface StartCallEditFormProps {
setExtractionPrompt: (value: string) => void;
variables: ExtractionVariable[];
setVariables: (vars: ExtractionVariable[]) => void;
callTagsEnabled: boolean;
setCallTagsEnabled: (value: boolean) => void;
callTagsPrompt: string;
setCallTagsPrompt: (value: string) => void;
toolUuids: string[];
setToolUuids: (value: string[]) => void;
documentUuids: string[];
@ -72,6 +76,8 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
const [extractionEnabled, setExtractionEnabled] = useState(data.extraction_enabled ?? false);
const [extractionPrompt, setExtractionPrompt] = useState(data.extraction_prompt ?? "");
const [variables, setVariables] = useState<ExtractionVariable[]>(data.extraction_variables ?? []);
const [callTagsEnabled, setCallTagsEnabled] = useState(data.call_tags_enabled ?? false);
const [callTagsPrompt, setCallTagsPrompt] = useState(data.call_tags_prompt ?? "");
const [toolUuids, setToolUuids] = useState<string[]>(data.tool_uuids ?? []);
const [documentUuids, setDocumentUuids] = useState<string[]>(data.document_uuids ?? []);
@ -96,6 +102,8 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
extraction_enabled: extractionEnabled,
extraction_prompt: extractionPrompt,
extraction_variables: variables,
call_tags_enabled: callTagsEnabled,
call_tags_prompt: callTagsPrompt,
tool_uuids: toolUuids.length > 0 ? toolUuids : undefined,
document_uuids: documentUuids.length > 0 ? documentUuids : undefined,
});
@ -119,6 +127,8 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
setExtractionEnabled(data.extraction_enabled ?? false);
setExtractionPrompt(data.extraction_prompt ?? "");
setVariables(data.extraction_variables ?? []);
setCallTagsEnabled(data.call_tags_enabled ?? false);
setCallTagsPrompt(data.call_tags_prompt ?? "");
setToolUuids(data.tool_uuids ?? []);
setDocumentUuids(data.document_uuids ?? []);
}
@ -138,6 +148,8 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
setExtractionEnabled(data.extraction_enabled ?? false);
setExtractionPrompt(data.extraction_prompt ?? "");
setVariables(data.extraction_variables ?? []);
setCallTagsEnabled(data.call_tags_enabled ?? false);
setCallTagsPrompt(data.call_tags_prompt ?? "");
setToolUuids(data.tool_uuids ?? []);
setDocumentUuids(data.document_uuids ?? []);
}
@ -241,6 +253,10 @@ export const StartCall = memo(({ data, selected, id }: StartCallNodeProps) => {
setExtractionPrompt={setExtractionPrompt}
variables={variables}
setVariables={setVariables}
callTagsEnabled={callTagsEnabled}
setCallTagsEnabled={setCallTagsEnabled}
callTagsPrompt={callTagsPrompt}
setCallTagsPrompt={setCallTagsPrompt}
toolUuids={toolUuids}
setToolUuids={setToolUuids}
documentUuids={documentUuids}
@ -275,6 +291,10 @@ const StartCallEditForm = ({
setExtractionPrompt,
variables,
setVariables,
callTagsEnabled,
setCallTagsEnabled,
callTagsPrompt,
setCallTagsPrompt,
toolUuids,
setToolUuids,
documentUuids,
@ -411,13 +431,16 @@ const StartCallEditForm = ({
<div className="border rounded-md p-3 mt-2 space-y-2 bg-muted/20">
<Label>Extraction Prompt</Label>
<Label className="text-xs text-muted-foreground">
Provide an overall extraction prompt that guides how variables should be extracted from the conversation.
Provide an extraction prompt that guides how variables should be extracted from the conversation.
Example: You are given transcript of a conversation between a home owner and an insurance provider.
Extract the below variables.
</Label>
<Textarea
value={extractionPrompt}
onChange={(e) => setExtractionPrompt(e.target.value)}
className="min-h-[80px] max-h-[200px] resize-none"
style={{ overflowY: 'auto' }}
placeholder="Example: You are given transcript of a conversation between a home owner and an insurance provider. Extract the below variables."
/>
<Label>Variables</Label>
@ -461,6 +484,32 @@ const StartCallEditForm = ({
</div>
)}
{/* Call Tags Section */}
<div className="flex items-center space-x-2 pt-2">
<Switch id="enable-call-tags" checked={callTagsEnabled} onCheckedChange={setCallTagsEnabled} />
<Label htmlFor="enable-call-tags">Enable Call Tags Extraction</Label>
<Label className="text-xs text-muted-foreground ml-2">
Extract tags from the conversation at this step to categorize the call.
</Label>
</div>
{callTagsEnabled && (
<div className="border rounded-md p-3 mt-2 space-y-2 bg-muted/20">
<Label>Call Tags Prompt</Label>
<Label className="text-xs text-muted-foreground">
Provide a prompt that guides how call tags should be extracted from the conversation.
Example: Extract tags that describe the outcome and topics discussed in this call.
</Label>
<Textarea
value={callTagsPrompt}
onChange={(e) => setCallTagsPrompt(e.target.value)}
className="min-h-[80px] max-h-[200px] resize-none"
style={{ overflowY: 'auto' }}
placeholder="Example: Extract tags that describe the outcome and topics discussed in this call."
/>
</div>
)}
{/* Tools Section */}
<div className="pt-4 border-t mt-4">
<ToolSelector

View file

@ -21,6 +21,8 @@ export type FlowNodeData = {
extraction_enabled?: boolean;
extraction_prompt?: string;
extraction_variables?: ExtractionVariable[];
call_tags_enabled?: boolean;
call_tags_prompt?: string;
add_global_prompt?: boolean;
wait_for_user_greeting?: boolean;
detect_voicemail?: boolean;