mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-27 04:18:35 +02:00
Internet Search Tool (#1666)
--------- Co-authored-by: Weves <chrisweaver101@gmail.com>
This commit is contained in:
@@ -53,6 +53,10 @@ function findImageGenerationTool(tools: ToolSnapshot[]) {
|
||||
return tools.find((tool) => tool.in_code_tool_id === "ImageGenerationTool");
|
||||
}
|
||||
|
||||
function findInternetSearchTool(tools: ToolSnapshot[]) {
|
||||
return tools.find((tool) => tool.in_code_tool_id === "InternetSearchTool");
|
||||
}
|
||||
|
||||
function SubLabel({ children }: { children: string | JSX.Element }) {
|
||||
return <div className="text-sm text-subtle mb-2">{children}</div>;
|
||||
}
|
||||
@@ -150,16 +154,20 @@ export function AssistantEditor({
|
||||
const imageGenerationTool = providerSupportingImageGenerationExists
|
||||
? findImageGenerationTool(tools)
|
||||
: undefined;
|
||||
const internetSearchTool = findInternetSearchTool(tools);
|
||||
|
||||
const customTools = tools.filter(
|
||||
(tool) =>
|
||||
tool.in_code_tool_id !== searchTool?.in_code_tool_id &&
|
||||
tool.in_code_tool_id !== imageGenerationTool?.in_code_tool_id
|
||||
tool.in_code_tool_id !== imageGenerationTool?.in_code_tool_id &&
|
||||
tool.in_code_tool_id !== internetSearchTool?.in_code_tool_id
|
||||
);
|
||||
|
||||
const availableTools = [
|
||||
...customTools,
|
||||
...(searchTool ? [searchTool] : []),
|
||||
...(imageGenerationTool ? [imageGenerationTool] : []),
|
||||
...(internetSearchTool ? [internetSearchTool] : []),
|
||||
];
|
||||
const enabledToolsMap: { [key: number]: boolean } = {};
|
||||
availableTools.forEach((tool) => {
|
||||
@@ -666,6 +674,17 @@ export function AssistantEditor({
|
||||
</>
|
||||
)}
|
||||
|
||||
{internetSearchTool && (
|
||||
<BooleanFormField
|
||||
noPadding
|
||||
name={`enabled_tools_map.${internetSearchTool.id}`}
|
||||
label={internetSearchTool.display_name}
|
||||
onChange={() => {
|
||||
toggleToolInValues(internetSearchTool.id);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{customTools.length > 0 && (
|
||||
<>
|
||||
{customTools.map((tool) => (
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Bubble } from "@/components/Bubble";
|
||||
import { ToolSnapshot } from "@/lib/tools/interfaces";
|
||||
import { FiImage, FiSearch } from "react-icons/fi";
|
||||
import { FiImage, FiSearch, FiGlobe } from "react-icons/fi";
|
||||
|
||||
export function ToolsDisplay({ tools }: { tools: ToolSnapshot[] }) {
|
||||
return (
|
||||
@@ -15,6 +15,9 @@ export function ToolsDisplay({ tools }: { tools: ToolSnapshot[] }) {
|
||||
} else if (tool.name === "ImageGenerationTool") {
|
||||
toolName = "Image Generation";
|
||||
toolIcon = <FiImage className="mr-1 my-auto" />;
|
||||
} else if (tool.name === "InternetSearchTool") {
|
||||
toolName = "Internet Search";
|
||||
toolIcon = <FiGlobe className="mr-1 my-auto" />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
DocumentMetadataBlock,
|
||||
buildDocumentSummaryDisplay,
|
||||
} from "@/components/search/DocumentDisplay";
|
||||
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
|
||||
|
||||
interface DocumentDisplayProps {
|
||||
document: DanswerDocument;
|
||||
@@ -30,24 +31,25 @@ export function ChatDocumentDisplay({
|
||||
setPopup,
|
||||
tokenLimitReached,
|
||||
}: DocumentDisplayProps) {
|
||||
// Consider reintroducing null scored docs in the future
|
||||
if (document.score === null) {
|
||||
return null;
|
||||
}
|
||||
const isInternet = document.is_internet;
|
||||
|
||||
return (
|
||||
<div key={document.semantic_identifier} className="text-sm px-3">
|
||||
<div className="flex relative w-full overflow-y-visible">
|
||||
<a
|
||||
className={
|
||||
"rounded-lg flex font-bold flex-shrink truncate " +
|
||||
"rounded-lg flex font-bold flex-shrink truncate items-center " +
|
||||
(document.link ? "" : "pointer-events-none")
|
||||
}
|
||||
href={document.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SourceIcon sourceType={document.source_type} iconSize={18} />
|
||||
{isInternet ? (
|
||||
<InternetSearchIcon url={document.link} />
|
||||
) : (
|
||||
<SourceIcon sourceType={document.source_type} iconSize={18} />
|
||||
)}
|
||||
<p className="overflow-hidden text-ellipsis mx-2 my-auto text-sm ">
|
||||
{document.semantic_identifier || document.document_id}
|
||||
</p>
|
||||
@@ -73,29 +75,16 @@ export function ChatDocumentDisplay({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`
|
||||
text-xs
|
||||
text-emphasis
|
||||
bg-hover
|
||||
rounded
|
||||
p-0.5
|
||||
w-fit
|
||||
my-auto
|
||||
select-none
|
||||
my-auto
|
||||
mr-2`}
|
||||
>
|
||||
{Math.abs(document.score).toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DocumentSelector
|
||||
isSelected={isSelected}
|
||||
handleSelect={() => handleSelect(document.document_id)}
|
||||
isDisabled={tokenLimitReached && !isSelected}
|
||||
/>
|
||||
{!isInternet && (
|
||||
<DocumentSelector
|
||||
isSelected={isSelected}
|
||||
handleSelect={() => handleSelect(document.document_id)}
|
||||
isDisabled={tokenLimitReached && !isSelected}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="mt-1">
|
||||
|
@@ -530,7 +530,11 @@ export function checkAnyAssistantHasSearch(
|
||||
}
|
||||
|
||||
export function personaIncludesRetrieval(selectedPersona: Persona) {
|
||||
return selectedPersona.num_chunks !== 0;
|
||||
return selectedPersona.tools.some(
|
||||
(tool) =>
|
||||
tool.in_code_tool_id &&
|
||||
["SearchTool", "InternetSearchTool"].includes(tool.in_code_tool_id)
|
||||
);
|
||||
}
|
||||
|
||||
const PARAMS_TO_SKIP = [
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
FiChevronRight,
|
||||
FiChevronLeft,
|
||||
FiTool,
|
||||
FiGlobe,
|
||||
} from "react-icons/fi";
|
||||
import { FeedbackType } from "../types";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@@ -25,6 +26,7 @@ import { ChatFileType, FileDescriptor, ToolCallMetadata } from "../interfaces";
|
||||
import {
|
||||
IMAGE_GENERATION_TOOL_NAME,
|
||||
SEARCH_TOOL_NAME,
|
||||
INTERNET_SEARCH_TOOL_NAME,
|
||||
} from "../tools/constants";
|
||||
import { ToolRunDisplay } from "../tools/ToolRunningAnimation";
|
||||
import { Hoverable } from "@/components/Hoverable";
|
||||
@@ -39,11 +41,12 @@ import Prism from "prismjs";
|
||||
import "prismjs/themes/prism-tomorrow.css";
|
||||
import "./custom-code-styles.css";
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { Button } from "@tremor/react";
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
|
||||
|
||||
const TOOLS_WITH_CUSTOM_HANDLING = [
|
||||
SEARCH_TOOL_NAME,
|
||||
INTERNET_SEARCH_TOOL_NAME,
|
||||
IMAGE_GENERATION_TOOL_NAME,
|
||||
];
|
||||
|
||||
@@ -149,6 +152,9 @@ export const AIMessage = ({
|
||||
content = trimIncompleteCodeSection(content);
|
||||
}
|
||||
|
||||
const danswerSearchToolEnabledForPersona = currentPersona.tools.some(
|
||||
(tool) => tool.in_code_tool_id === SEARCH_TOOL_NAME
|
||||
);
|
||||
const shouldShowLoader =
|
||||
!toolCall || (toolCall.tool_name === SEARCH_TOOL_NAME && !content);
|
||||
const defaultLoader = shouldShowLoader ? (
|
||||
@@ -200,36 +206,37 @@ export const AIMessage = ({
|
||||
</div>
|
||||
|
||||
<div className="w-message-xs 2xl:w-message-sm 3xl:w-message-default break-words mt-1 ml-8">
|
||||
{(!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME) && (
|
||||
<>
|
||||
{query !== undefined &&
|
||||
handleShowRetrieved !== undefined &&
|
||||
isCurrentlyShowingRetrieved !== undefined &&
|
||||
!retrievalDisabled && (
|
||||
<div className="my-1">
|
||||
<SearchSummary
|
||||
query={query}
|
||||
hasDocs={hasDocs || false}
|
||||
messageId={messageId}
|
||||
isCurrentlyShowingRetrieved={
|
||||
isCurrentlyShowingRetrieved
|
||||
}
|
||||
handleShowRetrieved={handleShowRetrieved}
|
||||
handleSearchQueryEdit={handleSearchQueryEdit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{handleForceSearch &&
|
||||
content &&
|
||||
query === undefined &&
|
||||
!hasDocs &&
|
||||
!retrievalDisabled && (
|
||||
<div className="my-1">
|
||||
<SkippedSearch handleForceSearch={handleForceSearch} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{(!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME) &&
|
||||
danswerSearchToolEnabledForPersona && (
|
||||
<>
|
||||
{query !== undefined &&
|
||||
handleShowRetrieved !== undefined &&
|
||||
isCurrentlyShowingRetrieved !== undefined &&
|
||||
!retrievalDisabled && (
|
||||
<div className="my-1">
|
||||
<SearchSummary
|
||||
query={query}
|
||||
hasDocs={hasDocs || false}
|
||||
messageId={messageId}
|
||||
isCurrentlyShowingRetrieved={
|
||||
isCurrentlyShowingRetrieved
|
||||
}
|
||||
handleShowRetrieved={handleShowRetrieved}
|
||||
handleSearchQueryEdit={handleSearchQueryEdit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{handleForceSearch &&
|
||||
content &&
|
||||
query === undefined &&
|
||||
!hasDocs &&
|
||||
!retrievalDisabled && (
|
||||
<div className="my-1">
|
||||
<SkippedSearch handleForceSearch={handleForceSearch} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{toolCall &&
|
||||
!TOOLS_WITH_CUSTOM_HANDLING.includes(toolCall.tool_name) && (
|
||||
@@ -258,6 +265,20 @@ export const AIMessage = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{toolCall && toolCall.tool_name === INTERNET_SEARCH_TOOL_NAME && (
|
||||
<div className="my-2">
|
||||
<ToolRunDisplay
|
||||
toolName={
|
||||
toolCall.tool_result
|
||||
? `Searched the internet`
|
||||
: `Searching the internet`
|
||||
}
|
||||
toolLogo={<FiGlobe size={15} className="my-auto mr-1" />}
|
||||
isRunning={!toolCall.tool_result}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{content ? (
|
||||
<>
|
||||
<FileDisplay files={files || []} />
|
||||
@@ -317,12 +338,16 @@ export const AIMessage = ({
|
||||
.filter(([_, document]) => document.semantic_identifier)
|
||||
.map(([citationKey, document], ind) => {
|
||||
const display = (
|
||||
<div className="max-w-350 text-ellipsis flex text-sm border border-border py-1 px-2 rounded flex">
|
||||
<div className="max-w-350 text-ellipsis text-sm border border-border py-1 px-2 rounded flex">
|
||||
<div className="mr-1 my-auto">
|
||||
<SourceIcon
|
||||
sourceType={document.source_type}
|
||||
iconSize={16}
|
||||
/>
|
||||
{document.is_internet ? (
|
||||
<InternetSearchIcon url={document.link} />
|
||||
) : (
|
||||
<SourceIcon
|
||||
sourceType={document.source_type}
|
||||
iconSize={16}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
[{citationKey}] {document!.semantic_identifier}
|
||||
</div>
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export const SEARCH_TOOL_NAME = "run_search";
|
||||
export const INTERNET_SEARCH_TOOL_NAME = "run_internet_search";
|
||||
export const IMAGE_GENERATION_TOOL_NAME = "run_image_generation";
|
||||
|
9
web/src/components/InternetSearchIcon.tsx
Normal file
9
web/src/components/InternetSearchIcon.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
export function InternetSearchIcon({ url }: { url: string }) {
|
||||
return (
|
||||
<img
|
||||
className="rounded-full w-[18px] h-[18px]"
|
||||
src={`https://www.google.com/s2/favicons?sz=128&domain=${url}`}
|
||||
alt="favicon"
|
||||
/>
|
||||
);
|
||||
}
|
@@ -11,6 +11,7 @@ export const SearchType = {
|
||||
SEMANTIC: "semantic",
|
||||
KEYWORD: "keyword",
|
||||
AUTOMATIC: "automatic",
|
||||
INTERNET: "internet",
|
||||
};
|
||||
export type SearchType = (typeof SearchType)[keyof typeof SearchType];
|
||||
|
||||
@@ -48,6 +49,7 @@ export interface DanswerDocument {
|
||||
metadata: { [key: string]: string };
|
||||
updated_at: string | null;
|
||||
db_doc_id?: number;
|
||||
is_internet: boolean;
|
||||
}
|
||||
|
||||
export interface DocumentInfoPacket {
|
||||
|
@@ -39,7 +39,6 @@ import {
|
||||
import { ValidSources } from "./types";
|
||||
import { SourceCategory, SourceMetadata } from "./search/interfaces";
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import internal from "stream";
|
||||
|
||||
interface PartialSourceMetadata {
|
||||
icon: React.FC<{ size?: number; className?: string }>;
|
||||
@@ -232,6 +231,11 @@ const SOURCE_METADATA_MAP: SourceMap = {
|
||||
displayName: "Google Storage",
|
||||
category: SourceCategory.AppConnection,
|
||||
},
|
||||
not_applicable: {
|
||||
icon: GlobeIcon,
|
||||
displayName: "Internet",
|
||||
category: SourceCategory.ImportedKnowledge,
|
||||
},
|
||||
};
|
||||
|
||||
function fillSourceMetadata(
|
||||
|
@@ -1,6 +1,7 @@
|
||||
export interface ToolSnapshot {
|
||||
id: number;
|
||||
name: string;
|
||||
display_name: string;
|
||||
description: string;
|
||||
|
||||
// only specified for Custom Tools. OpenAPI schema which represents
|
||||
|
@@ -63,7 +63,8 @@ export type ValidSources =
|
||||
| "s3"
|
||||
| "r2"
|
||||
| "google_cloud_storage"
|
||||
| "oci_storage";
|
||||
| "oci_storage"
|
||||
| "not_applicable";
|
||||
|
||||
export type ValidInputTypes = "load_state" | "poll" | "event";
|
||||
export type ValidStatuses =
|
||||
|
Reference in New Issue
Block a user