Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/src/components/tools/ToolTemplateCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Database, Globe, Cpu, Bot, Code } from 'lucide-react';
import { Database, Globe, Cpu, Bot, Code, Search } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { cn } from '@/lib/utils';
import type { ToolTemplate } from '@/types/toolTemplate.types';
Expand All @@ -14,6 +14,7 @@ const iconMap = {
cpu: Cpu,
bot: Bot,
code: Code,
search: Search,
};

export function ToolTemplateCard({ template, onClick }: ToolTemplateCardProps) {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/config/toolTemplates.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@
"Load Conversation Data"
]
},
{
"id": "web_search",
"name": "Web Search",
"description": "Search the web for live information via Google Programmable Search or Perplexity AI.",
"icon": "search",
"toolTypes": [
"Google Search",
"Perplexity Search"
]
},
{
"id": "run_agent",
"name": "Run AI Agent",
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types/agent.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export type ToolType =
| "Get Conversation Data"
| "Set Conversation Data"
| "Load Conversation Data"
| "Google Search"
| "Perplexity Search"
| "Transcription"
/**
* @deprecated Legacy value kept for backward compatibility with saved docs.
Expand Down
3 changes: 2 additions & 1 deletion huf/ai/app_seeding/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ def upsert_agent(data: dict, source_app: str, source_file: str) -> tuple:
"Cancel Document", "Get Amended Document", "Custom Function", "App Provided",
"Attach File to Document", "Get Report Result", "Get Value", "Set Value",
"GET", "POST", "Run Agent", "Client Side Tool", "Get Conversation Data",
"Set Conversation Data", "Load Conversation Data"
"Set Conversation Data", "Load Conversation Data", "Google Search",
"Perplexity Search"
]

def upsert_tool(data: dict, source_app: str, source_file: str) -> tuple:
Expand Down
2 changes: 2 additions & 0 deletions huf/ai/flow_tool_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ def _resolve_function_path(tool_doc: dict) -> str | None:
"Get Conversation Data": "huf.ai.sdk_tools.handle_get_conversation_data",
"Set Conversation Data": "huf.ai.sdk_tools.handle_set_conversation_data",
"Load Conversation Data": "huf.ai.sdk_tools.handle_load_conversation_data",
"Google Search": "huf.ai.sdk_tools.handle_google_search",
"Perplexity Search": "huf.ai.sdk_tools.handle_perplexity_search",
}

return type_to_handler.get(tool_type)
Expand Down
132 changes: 131 additions & 1 deletion huf/ai/sdk_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ def create_agent_tools(agent) -> list[FunctionTool]:
function_path = "huf.ai.sdk_tools.handle_set_conversation_data"
elif function_doc.types == "Load Conversation Data":
function_path = "huf.ai.sdk_tools.handle_load_conversation_data"
elif function_doc.types == "Google Search":
function_path = "huf.ai.sdk_tools.handle_google_search"
elif function_doc.types == "Perplexity Search":
function_path = "huf.ai.sdk_tools.handle_perplexity_search"

else:
continue
Expand Down Expand Up @@ -2325,4 +2329,130 @@ async def handle_transcribe_audio(
except Exception as e:
# Hard transcription failure: retain Error Log for admin attention.
frappe.log_error(f"Audio transcription error: {e!s}", "Audio Transcription Tool")
return {"success": False, "error": str(e)}
return {"success": False, "error": str(e)}

def handle_google_search(query: str, max_results: int = 5, **kwargs):
"""
Perform a web search using Google Programmable Search Engine.

Reads the API key and search engine ID from site_config
(``google_pse_api_key`` and ``google_pse_engine_id``).

Args:
query: The search query string
max_results: Maximum number of results to return (default: 5, max: 10)

Returns:
dict: {
"success": bool,
"query": str,
"results": list, # [{"title", "link", "snippet"}, ...]
"total_results": int,
"error": str
}
"""
site_config = frappe.get_site_config()
api_key = site_config.get("google_pse_api_key")
engine_id = site_config.get("google_pse_engine_id")

if not api_key:
frappe.throw(
_("Google Search is not configured. Please set 'google_pse_api_key' in site config.")
)
if not engine_id:
frappe.throw(
_("Google Search is not configured. Please set 'google_pse_engine_id' in site config.")
)

try:
num = max(1, min(int(max_results or 5), 10))

response = requests.get(
"https://www.googleapis.com/customsearch/v1",
params={"key": api_key, "cx": engine_id, "q": query, "num": num},
timeout=30,
)
response.raise_for_status()

items = response.json().get("items", [])
results = [
{
"title": item.get("title"),
"link": item.get("link"),
"snippet": item.get("snippet"),
}
for item in items
]

return {
"success": True,
"query": query,
"results": results,
"total_results": len(results),
}
except Exception as e:
logger.warning(f"handle_google_search failed: {e!s}")
return {"success": False, "error": str(e)}


def handle_perplexity_search(query: str, **kwargs):
"""
Perform a web search using a Perplexity sonar model via LiteLLM.

Reads the API key from the ``perplexity_api_key`` site_config value,
falling back to the ``PERPLEXITY_API_KEY`` environment variable.

Args:
query: The search query string

Returns:
dict: {
"success": bool,
"query": str,
"answer": str, # Grounded answer text from the sonar model
"citations": list, # Source URLs cited by the model
"model": str,
"error": str
}
"""
import os

site_config = frappe.get_site_config()
api_key = site_config.get("perplexity_api_key") or os.environ.get("PERPLEXITY_API_KEY")

if not api_key:
frappe.throw(
_(
"Perplexity Search is not configured. Please set 'perplexity_api_key' "
"in site config or the PERPLEXITY_API_KEY environment variable."
)
)

try:
from litellm import completion

# LiteLLM routes Perplexity credentials via environment variable
os.environ["PERPLEXITY_API_KEY"] = api_key

response = completion(
model="perplexity/sonar",
messages=[{"role": "user", "content": query}],
)

message = response.choices[0].message
citations = (
getattr(response, "citations", None)
or getattr(message, "citations", None)
or []
)

return {
"success": True,
"query": query,
"answer": message.content,
"citations": citations,
"model": "perplexity/sonar",
}
except Exception as e:
logger.warning(f"handle_perplexity_search failed: {e!s}")
return {"success": False, "error": str(e)}
4 changes: 2 additions & 2 deletions huf/huf/doctype/agent_tool_function/agent_tool_function.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"fieldname": "types",
"fieldtype": "Select",
"label": "Types",
"options": "\nGet Document\nGet Multiple Documents\nGet List\nCreate Document\nCreate Multiple Documents\nUpdate Document\nUpdate Multiple Documents\nDelete Document\nDelete Multiple Documents\nSubmit Document\nCancel Document\nGet Amended Document\nCustom Function\nApp Provided\nAttach File to Document\nGet Report Result\nGet Value\nSet Value\nGET\nPOST\nRun Agent\nClient Side Tool\nGet Conversation Data\nSet Conversation Data\nLoad Conversation Data"
"options": "\nGet Document\nGet Multiple Documents\nGet List\nCreate Document\nCreate Multiple Documents\nUpdate Document\nUpdate Multiple Documents\nDelete Document\nDelete Multiple Documents\nSubmit Document\nCancel Document\nGet Amended Document\nCustom Function\nApp Provided\nAttach File to Document\nGet Report Result\nGet Value\nSet Value\nGET\nPOST\nRun Agent\nClient Side Tool\nGet Conversation Data\nSet Conversation Data\nLoad Conversation Data\nGoogle Search\nPerplexity Search"
},
{
"depends_on": "eval: [\n 'Get Document',\n 'Get Multiple Documents',\n 'Get List',\n 'Create Document',\n 'Create Multiple Documents',\n 'Update Document',\n 'Update Multiple Documents',\n 'Delete Document',\n 'Delete Multiple Documents',\n 'Submit Document',\n 'Cancel Document',\n 'Get Amended Document',\n 'Attach File to Document',\n 'Get Report Result',\n 'Get Value',\n 'Set Value'\n].includes(doc.types)",
Expand Down Expand Up @@ -192,7 +192,7 @@
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2026-02-22 22:21:36.079707",
"modified": "2026-07-26 04:48:07.000000",
"modified_by": "Administrator",
"module": "Huf",
"name": "Agent Tool Function",
Expand Down
31 changes: 31 additions & 0 deletions huf/huf/doctype/agent_tool_function/agent_tool_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,37 @@ def prepare_function_params(self):
"additionalProperties": False
}

elif self.types == "Google Search":
params = {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query string."
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return (default: 5).",
"default": 5
}
},
"required": ["query"],
"additionalProperties": False
}

elif self.types == "Perplexity Search":
params = {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query string."
}
},
"required": ["query"],
"additionalProperties": False
}

else:
params = self.build_params_json_from_table()

Expand Down