From 4c642a0bb8ccb461cc849c3921b679e66193b84b Mon Sep 17 00:00:00 2001 From: ad-elias Date: Thu, 4 Jul 2024 08:57:26 +0200 Subject: [PATCH] Text-to-SQL proof of concept (#5788) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added: - An "Ask AI" command to the command menu. - A simple GraphQL resolver that converts the user's question into a relevant SQL query using an LLM, runs the query, and returns the result. Screenshot 2024-06-09 at 20 53 09 No security concerns have been addressed, this is only a proof-of-concept and not intended to be enabled in production. All changes are behind a feature flag called `IS_ASK_AI_ENABLED`. --------- Co-authored-by: FĂ©lix Malfait --- .../twenty-front/src/generated/graphql.tsx | 57 ++ .../components/RightDrawerAIChat.tsx | 54 ++ .../hooks/useOpenCopilotRightDrawer.ts | 14 + .../right-drawer/states/copilotQueryState.ts | 6 + .../command-menu/components/CommandMenu.tsx | 45 +- .../modules/search/queries/getTextToSQL.ts | 11 + .../ui/input/components/AutosizeTextInput.tsx | 9 +- .../components/RightDrawerRouter.tsx | 5 + .../constants/RightDrawerPageIcons.ts | 1 + .../constants/RightDrawerPageTitles.ts | 1 + .../right-drawer/types/RightDrawerPages.ts | 1 + .../modules/ui/layout/tab/components/Tab.tsx | 6 +- .../ui/layout/tab/components/TabList.tsx | 4 +- .../modules/workspace/types/FeatureFlagKey.ts | 3 +- packages/twenty-server/.env.example | 2 +- packages/twenty-server/package.json | 7 +- .../workspace-query-runner.service.ts | 36 +- .../ai-sql-query/ai-sql-query.module.ts | 30 + .../ai-sql-query.prompt-templates.ts | 14 + .../ai-sql-query/ai-sql-query.resolver.ts | 64 +++ .../ai-sql-query/ai-sql-query.service.ts | 253 +++++++++ .../dtos/ai-sql-query-result.dto.ts | 17 + .../engine/core-modules/core-engine.module.ts | 2 + .../feature-flag/feature-flag.entity.ts | 1 + .../environment/environment-variables.ts | 12 + .../integrations/integrations.module.ts | 12 + .../llm-prompt-template-driver.interface.ts | 5 + .../llm-chat-model/drivers/openai.driver.ts | 22 + .../interfaces/llm-chat-model.interface.ts | 14 + .../llm-chat-model.constants.ts | 1 + .../llm-chat-model.module-factory.ts | 19 + .../llm-chat-model/llm-chat-model.module.ts | 35 ++ .../llm-chat-model/llm-chat-model.service.ts | 16 + .../llm-tracing/drivers/console.driver.ts | 25 + .../llm-tracing-driver.interface.ts | 5 + .../llm-tracing/drivers/langfuse.driver.ts | 26 + .../interfaces/llm-tracing.interface.ts | 26 + .../llm-tracing/llm-tracing.constants.ts | 1 + .../llm-tracing/llm-tracing.module-factory.ts | 34 ++ .../llm-tracing/llm-tracing.module.ts | 39 ++ .../llm-tracing/llm-tracing.service.ts | 16 + .../object-metadata.constants.ts | 1 + .../commands/add-standard-id.command.ts | 2 + .../display/icon/components/TablerIcons.ts | 4 + .../self-hosting/self-hosting-var.mdx | 9 +- yarn.lock | 536 +++++++++++++++++- 46 files changed, 1463 insertions(+), 40 deletions(-) create mode 100644 packages/twenty-front/src/modules/activities/copilot/right-drawer/components/RightDrawerAIChat.tsx create mode 100644 packages/twenty-front/src/modules/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer.ts create mode 100644 packages/twenty-front/src/modules/activities/copilot/right-drawer/states/copilotQueryState.ts create mode 100644 packages/twenty-front/src/modules/search/queries/getTextToSQL.ts create mode 100644 packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.module.ts create mode 100644 packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.prompt-templates.ts create mode 100644 packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.resolver.ts create mode 100644 packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.service.ts create mode 100644 packages/twenty-server/src/engine/core-modules/ai-sql-query/dtos/ai-sql-query-result.dto.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-chat-model/drivers/interfaces/llm-prompt-template-driver.interface.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-chat-model/drivers/openai.driver.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-chat-model/interfaces/llm-chat-model.interface.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.constants.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.module-factory.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.module.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.service.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-tracing/drivers/console.driver.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-tracing/drivers/interfaces/llm-tracing-driver.interface.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-tracing/drivers/langfuse.driver.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-tracing/interfaces/llm-tracing.interface.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.constants.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.module-factory.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.module.ts create mode 100644 packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.service.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.constants.ts diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index fd4d821ec3..aef71e4d86 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -20,6 +20,13 @@ export type Scalars = { Upload: any; }; +export type AisqlQueryResult = { + __typename?: 'AISQLQueryResult'; + queryFailedErrorMessage?: Maybe; + sqlQuery: Scalars['String']; + sqlQueryResult?: Maybe; +}; + export type ActivateWorkspaceInput = { displayName?: InputMaybe; }; @@ -526,6 +533,7 @@ export type Query = { currentUser: User; currentWorkspace: Workspace; findWorkspaceFromInviteHash: Workspace; + getAISQLQuery: AisqlQueryResult; getPostgresCredentials?: Maybe; getProductPrices: ProductPricesEntity; getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal; @@ -559,6 +567,11 @@ export type QueryFindWorkspaceFromInviteHashArgs = { }; +export type QueryGetAisqlQueryArgs = { + text: Scalars['String']; +}; + + export type QueryGetProductPricesArgs = { product: Scalars['String']; }; @@ -1264,6 +1277,13 @@ export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string] export type SkipSyncEmailOnboardingStepMutation = { __typename?: 'Mutation', skipSyncEmailOnboardingStep: { __typename?: 'OnboardingStepSuccess', success: boolean } }; +export type GetAisqlQueryQueryVariables = Exact<{ + text: Scalars['String']; +}>; + + +export type GetAisqlQueryQuery = { __typename?: 'Query', getAISQLQuery: { __typename?: 'AISQLQueryResult', sqlQuery: string, sqlQueryResult?: string | null, queryFailedErrorMessage?: string | null } }; + export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: string, currentCacheVersion?: string | null, workspaceMembersCount?: number | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: SubscriptionStatus, interval?: SubscriptionInterval | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }; export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>; @@ -2449,6 +2469,43 @@ export function useSkipSyncEmailOnboardingStepMutation(baseOptions?: Apollo.Muta export type SkipSyncEmailOnboardingStepMutationHookResult = ReturnType; export type SkipSyncEmailOnboardingStepMutationResult = Apollo.MutationResult; export type SkipSyncEmailOnboardingStepMutationOptions = Apollo.BaseMutationOptions; +export const GetAisqlQueryDocument = gql` + query GetAISQLQuery($text: String!) { + getAISQLQuery(text: $text) { + sqlQuery + sqlQueryResult + queryFailedErrorMessage + } +} + `; + +/** + * __useGetAisqlQueryQuery__ + * + * To run a query within a React component, call `useGetAisqlQueryQuery` and pass it any options that fit your needs. + * When your component renders, `useGetAisqlQueryQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetAisqlQueryQuery({ + * variables: { + * text: // value for 'text' + * }, + * }); + */ +export function useGetAisqlQueryQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetAisqlQueryDocument, options); + } +export function useGetAisqlQueryLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetAisqlQueryDocument, options); + } +export type GetAisqlQueryQueryHookResult = ReturnType; +export type GetAisqlQueryLazyQueryHookResult = ReturnType; +export type GetAisqlQueryQueryResult = Apollo.QueryResult; export const DeleteUserAccountDocument = gql` mutation DeleteUserAccount { deleteUser { diff --git a/packages/twenty-front/src/modules/activities/copilot/right-drawer/components/RightDrawerAIChat.tsx b/packages/twenty-front/src/modules/activities/copilot/right-drawer/components/RightDrawerAIChat.tsx new file mode 100644 index 0000000000..4ad2c423c7 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/copilot/right-drawer/components/RightDrawerAIChat.tsx @@ -0,0 +1,54 @@ +import styled from '@emotion/styled'; +import { useSetRecoilState } from 'recoil'; + +import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState'; +import { + AutosizeTextInput, + AutosizeTextInputVariant, +} from '@/ui/input/components/AutosizeTextInput'; + +const StyledContainer = styled.div` + box-sizing: border-box; + display: flex; + flex-direction: column; + height: 100%; + justify-content: flex-start; + overflow-y: auto; + position: relative; +`; + +const StyledChatArea = styled.div` + flex: 1; + display: flex; + flex-direction: column; + overflow-y: scroll; + padding: ${({ theme }) => theme.spacing(6)}; + padding-bottom: 0px; +`; + +const StyledNewMessageArea = styled.div` + display: flex; + flex-direction: column; + padding: ${({ theme }) => theme.spacing(6)}; + padding-top: 0px; +`; + +export const RightDrawerAIChat = () => { + const setCopilotQuery = useSetRecoilState(copilotQueryState); + + return ( + + {/* TODO */} + + { + setCopilotQuery(text); + }} + /> + + + ); +}; diff --git a/packages/twenty-front/src/modules/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer.ts b/packages/twenty-front/src/modules/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer.ts new file mode 100644 index 0000000000..5369451624 --- /dev/null +++ b/packages/twenty-front/src/modules/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer.ts @@ -0,0 +1,14 @@ +import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; +import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope'; +import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; +import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; + +export const useOpenCopilotRightDrawer = () => { + const { openRightDrawer } = useRightDrawer(); + const setHotkeyScope = useSetHotkeyScope(); + + return () => { + setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false }); + openRightDrawer(RightDrawerPages.Copilot); + }; +}; diff --git a/packages/twenty-front/src/modules/activities/copilot/right-drawer/states/copilotQueryState.ts b/packages/twenty-front/src/modules/activities/copilot/right-drawer/states/copilotQueryState.ts new file mode 100644 index 0000000000..ee56e8b0fe --- /dev/null +++ b/packages/twenty-front/src/modules/activities/copilot/right-drawer/states/copilotQueryState.ts @@ -0,0 +1,6 @@ +import { createState } from 'twenty-ui'; + +export const copilotQueryState = createState({ + key: 'activities/copilot-query', + defaultValue: '', +}); diff --git a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx index efd72c3b93..8b23973c5b 100644 --- a/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx +++ b/packages/twenty-front/src/modules/command-menu/components/CommandMenu.tsx @@ -1,10 +1,12 @@ import { useMemo, useRef } from 'react'; import styled from '@emotion/styled'; import { isNonEmptyString } from '@sniptt/guards'; -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; -import { Avatar, IconNotes } from 'twenty-ui'; +import { Avatar, IconNotes, IconSparkles } from 'twenty-ui'; +import { useOpenCopilotRightDrawer } from '@/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer'; +import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState'; import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer'; import { Activity } from '@/activities/types/Activity'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; @@ -21,6 +23,7 @@ import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { getLogoUrlFromDomainName } from '~/utils'; import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateILikeFiltersForCompositeFields'; import { isDefined } from '~/utils/isDefined'; @@ -248,8 +251,27 @@ export const CommandMenu = () => { callback: closeCommandMenu, }); - const selectableItemIds = matchingCreateCommand + const isCopilotEnabled = useIsFeatureEnabled('IS_COPILOT_ENABLED'); + const setCopilotQuery = useSetRecoilState(copilotQueryState); + const openCopilotRightDrawer = useOpenCopilotRightDrawer(); + + const copilotCommand: Command = { + id: 'copilot', + to: '', // TODO + Icon: IconSparkles, + label: 'Open Copilot', + type: CommandType.Navigate, + onCommandClick: () => { + setCopilotQuery(commandMenuSearch); + openCopilotRightDrawer(); + }, + }; + + const copilotCommands: Command[] = isCopilotEnabled ? [copilotCommand] : []; + + const selectableItemIds = copilotCommands .map((cmd) => cmd.id) + .concat(matchingCreateCommand.map((cmd) => cmd.id)) .concat(matchingNavigateCommand.map((cmd) => cmd.id)) .concat(people.map((person) => person.id)) .concat(companies.map((company) => company.id)) @@ -275,6 +297,7 @@ export const CommandMenu = () => { hotkeyScope={AppHotkeyScope.CommandMenu} onEnter={(itemId) => { const command = [ + ...copilotCommands, ...commandMenuCommands, ...otherCommands, ].find((cmd) => cmd.id === itemId); @@ -292,6 +315,22 @@ export const CommandMenu = () => { !activities.length && ( No results found )} + {isCopilotEnabled && ( + + + 2 + ? `"${commandMenuSearch}"` + : '' + }`} + onClick={copilotCommand.onCommandClick} + /> + + + )} {matchingCreateCommand.map((cmd) => ( diff --git a/packages/twenty-front/src/modules/search/queries/getTextToSQL.ts b/packages/twenty-front/src/modules/search/queries/getTextToSQL.ts new file mode 100644 index 0000000000..6758209824 --- /dev/null +++ b/packages/twenty-front/src/modules/search/queries/getTextToSQL.ts @@ -0,0 +1,11 @@ +import { gql } from '@apollo/client'; + +export const getCopilot = gql` + query GetAISQLQuery($text: String!) { + getAISQLQuery(text: $text) { + sqlQuery + sqlQueryResult + queryFailedErrorMessage + } + } +`; diff --git a/packages/twenty-front/src/modules/ui/input/components/AutosizeTextInput.tsx b/packages/twenty-front/src/modules/ui/input/components/AutosizeTextInput.tsx index b827b8e08f..eb9afd68a3 100644 --- a/packages/twenty-front/src/modules/ui/input/components/AutosizeTextInput.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/AutosizeTextInput.tsx @@ -30,6 +30,8 @@ type AutosizeTextInputProps = { value?: string; className?: string; onBlur?: () => void; + autoFocus?: boolean; + disabled?: boolean; }; const StyledContainer = styled.div` @@ -123,6 +125,8 @@ export const AutosizeTextInput = ({ value = '', className, onBlur, + autoFocus, + disabled, }: AutosizeTextInputProps) => { const [isFocused, setIsFocused] = useState(false); const [isHidden, setIsHidden] = useState( @@ -212,7 +216,9 @@ export const AutosizeTextInput = ({ {!isHidden && ( )} {variant === AutosizeTextInputVariant.Icon && ( diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx index c9b4ab44be..20c931f86a 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx @@ -2,6 +2,7 @@ import styled from '@emotion/styled'; import { useRecoilState, useRecoilValue } from 'recoil'; import { RightDrawerCalendarEvent } from '@/activities/calendar/right-drawer/components/RightDrawerCalendarEvent'; +import { RightDrawerAIChat } from '@/activities/copilot/right-drawer/components/RightDrawerAIChat'; import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread'; import { RightDrawerCreateActivity } from '@/activities/right-drawer/components/create/RightDrawerCreateActivity'; import { RightDrawerEditActivity } from '@/activities/right-drawer/components/edit/RightDrawerEditActivity'; @@ -50,6 +51,10 @@ const RIGHT_DRAWER_PAGES_CONFIG = { page: , topBar: , }, + [RightDrawerPages.Copilot]: { + page: , + topBar: , + }, }; export const RightDrawerRouter = () => { diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts index f1c46ba194..429436b660 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageIcons.ts @@ -6,4 +6,5 @@ export const RIGHT_DRAWER_PAGE_ICONS = { [RightDrawerPages.ViewEmailThread]: 'IconMail', [RightDrawerPages.ViewCalendarEvent]: 'IconCalendarEvent', [RightDrawerPages.ViewRecord]: 'Icon123', + [RightDrawerPages.Copilot]: 'IconSparkles', }; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts index 6edb8fec21..18517c80dd 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/constants/RightDrawerPageTitles.ts @@ -6,4 +6,5 @@ export const RIGHT_DRAWER_PAGE_TITLES = { [RightDrawerPages.ViewEmailThread]: 'Email Thread', [RightDrawerPages.ViewCalendarEvent]: 'Calendar Event', [RightDrawerPages.ViewRecord]: 'Record Editor', + [RightDrawerPages.Copilot]: 'Copilot', }; diff --git a/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts b/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts index 487b1a16f8..e579b65448 100644 --- a/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts +++ b/packages/twenty-front/src/modules/ui/layout/right-drawer/types/RightDrawerPages.ts @@ -4,4 +4,5 @@ export enum RightDrawerPages { ViewEmailThread = 'view-email-thread', ViewCalendarEvent = 'view-calendar-event', ViewRecord = 'view-record', + Copilot = 'copilot', } diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx index ff024d20f9..2bfc1dbfba 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx +++ b/packages/twenty-front/src/modules/ui/layout/tab/components/Tab.tsx @@ -11,7 +11,7 @@ type TabProps = { className?: string; onClick?: () => void; disabled?: boolean; - hasBetaPill?: boolean; + pill?: string; }; const StyledTab = styled.div<{ active?: boolean; disabled?: boolean }>` @@ -59,7 +59,7 @@ export const Tab = ({ onClick, className, disabled, - hasBetaPill, + pill, }: TabProps) => { const theme = useTheme(); return ( @@ -73,7 +73,7 @@ export const Tab = ({ {Icon && } {title} - {hasBetaPill && } + {pill && } ); diff --git a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx index ba93d808f9..0cd5b4bccc 100644 --- a/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx +++ b/packages/twenty-front/src/modules/ui/layout/tab/components/TabList.tsx @@ -15,7 +15,7 @@ type SingleTabProps = { id: string; hide?: boolean; disabled?: boolean; - hasBetaPill?: boolean; + pill?: string; }; type TabListProps = { @@ -62,7 +62,7 @@ export const TabList = ({ tabs, tabListId, loading }: TabListProps) => { setActiveTabId(tab.id); }} disabled={tab.disabled ?? loading} - hasBetaPill={tab.hasBetaPill} + pill={tab.pill} /> ))} diff --git a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts index cb6262c2d0..d9b76577cb 100644 --- a/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts +++ b/packages/twenty-front/src/modules/workspace/types/FeatureFlagKey.ts @@ -4,4 +4,5 @@ export type FeatureFlagKey = | 'IS_EVENT_OBJECT_ENABLED' | 'IS_AIRTABLE_INTEGRATION_ENABLED' | 'IS_POSTGRESQL_INTEGRATION_ENABLED' - | 'IS_STRIPE_INTEGRATION_ENABLED'; + | 'IS_STRIPE_INTEGRATION_ENABLED' + | 'IS_COPILOT_ENABLED'; diff --git a/packages/twenty-server/.env.example b/packages/twenty-server/.env.example index 8f4a61ab9e..8b2c6cac2f 100644 --- a/packages/twenty-server/.env.example +++ b/packages/twenty-server/.env.example @@ -72,4 +72,4 @@ SIGN_IN_PREFILLED=true # API_RATE_LIMITING_LIMIT= # MUTATION_MAXIMUM_AFFECTED_RECORDS=100 # CHROME_EXTENSION_ID=bggmipldbceihilonnbpgoeclgbkblkp -# PG_SSL_ALLOW_SELF_SIGNED=true +# PG_SSL_ALLOW_SELF_SIGNED=true \ No newline at end of file diff --git a/packages/twenty-server/package.json b/packages/twenty-server/package.json index 9884c04db4..7bd8ef037e 100644 --- a/packages/twenty-server/package.json +++ b/packages/twenty-server/package.json @@ -15,6 +15,8 @@ }, "dependencies": { "@graphql-yoga/nestjs": "patch:@graphql-yoga/nestjs@2.1.0#./patches/@graphql-yoga-nestjs-npm-2.1.0-cb509e6047.patch", + "@langchain/mistralai": "^0.0.24", + "@langchain/openai": "^0.1.3", "@nestjs/cache-manager": "^2.2.1", "@nestjs/devtools-integration": "^0.1.6", "@nestjs/graphql": "patch:@nestjs/graphql@12.1.1#./patches/@nestjs+graphql+12.1.1.patch", @@ -25,13 +27,16 @@ "graphql-middleware": "^6.1.35", "jsdom": "~22.1.0", "jwt-decode": "^4.0.0", + "langchain": "^0.2.6", + "langfuse-langchain": "^3.11.2", "lodash.differencewith": "^4.5.0", "lodash.omitby": "^4.6.0", "lodash.uniq": "^4.5.0", "lodash.uniqby": "^4.7.0", "passport": "^0.7.0", "psl": "^1.9.0", - "tsconfig-paths": "^4.2.0" + "tsconfig-paths": "^4.2.0", + "zod-to-json-schema": "^3.23.1" }, "devDependencies": { "@nestjs/cli": "10.3.0", diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts index 1a4c690df9..332fc08c7b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service.ts @@ -7,6 +7,7 @@ import { import { EventEmitter2 } from '@nestjs/event-emitter'; import isEmpty from 'lodash.isempty'; +import { DataSource } from 'typeorm'; import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface'; import { @@ -620,15 +621,12 @@ export class WorkspaceQueryRunnerService { return sanitizedRecord; } - async execute( - query: string, + async executeSQL( + workspaceDataSource: DataSource, workspaceId: string, - ): Promise { - const workspaceDataSource = - await this.workspaceDataSourceService.connectToWorkspaceDataSource( - workspaceId, - ); - + sqlQuery: string, + parameters?: any[], + ) { try { return await workspaceDataSource?.transaction( async (transactionManager) => { @@ -638,10 +636,7 @@ export class WorkspaceQueryRunnerService { )}; `); - const results = transactionManager.query( - `SELECT graphql.resolve($1);`, - [query], - ); + const results = transactionManager.query(sqlQuery, parameters); return results; }, @@ -655,6 +650,23 @@ export class WorkspaceQueryRunnerService { } } + async execute( + query: string, + workspaceId: string, + ): Promise { + const workspaceDataSource = + await this.workspaceDataSourceService.connectToWorkspaceDataSource( + workspaceId, + ); + + return this.executeSQL( + workspaceDataSource, + workspaceId, + `SELECT graphql.resolve($1);`, + [query], + ); + } + private async parseResult( graphqlResult: PGGraphQLResult | undefined, objectMetadataItem: ObjectMetadataInterface, diff --git a/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.module.ts b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.module.ts new file mode 100644 index 0000000000..f416a08987 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.module.ts @@ -0,0 +1,30 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; +import { UserModule } from 'src/engine/core-modules/user/user.module'; +import { AISQLQueryResolver } from 'src/engine/core-modules/ai-sql-query/ai-sql-query.resolver'; +import { AISQLQueryService } from 'src/engine/core-modules/ai-sql-query/ai-sql-query.service'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module'; +import { LLMChatModelModule } from 'src/engine/integrations/llm-chat-model/llm-chat-model.module'; +import { EnvironmentModule } from 'src/engine/integrations/environment/environment.module'; +import { LLMTracingModule } from 'src/engine/integrations/llm-tracing/llm-tracing.module'; +import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; +import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module'; +@Module({ + imports: [ + WorkspaceDataSourceModule, + WorkspaceQueryRunnerModule, + UserModule, + TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), + LLMChatModelModule, + LLMTracingModule, + EnvironmentModule, + ObjectMetadataModule, + WorkspaceSyncMetadataModule, + ], + exports: [], + providers: [AISQLQueryResolver, AISQLQueryService], +}) +export class AISQLQueryModule {} diff --git a/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.prompt-templates.ts b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.prompt-templates.ts new file mode 100644 index 0000000000..3b100c0b77 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.prompt-templates.ts @@ -0,0 +1,14 @@ +import { PromptTemplate } from '@langchain/core/prompts'; + +export const sqlGenerationPromptTemplate = PromptTemplate.fromTemplate<{ + llmOutputJsonSchema: string; + sqlCreateTableStatements: string; + userQuestion: string; +}>(`Always respond following this JSON Schema: {llmOutputJsonSchema} + +Based on the table schema below, write a PostgreSQL query that would answer the user's question. All column names must be enclosed in double quotes. + +{sqlCreateTableStatements} + +Question: {userQuestion} +SQL Query:`); diff --git a/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.resolver.ts b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.resolver.ts new file mode 100644 index 0000000000..6aa38399a9 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.resolver.ts @@ -0,0 +1,64 @@ +import { Args, Query, Resolver, ArgsType, Field } from '@nestjs/graphql'; +import { ForbiddenException, UseGuards } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; + +import { User } from 'src/engine/core-modules/user/user.entity'; +import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { + FeatureFlagEntity, + FeatureFlagKeys, +} from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; +import { AISQLQueryResult } from 'src/engine/core-modules/ai-sql-query/dtos/ai-sql-query-result.dto'; +import { AISQLQueryService } from 'src/engine/core-modules/ai-sql-query/ai-sql-query.service'; + +@ArgsType() +class GetAISQLQueryArgs { + @Field(() => String) + text: string; +} + +@UseGuards(JwtAuthGuard) +@Resolver(() => AISQLQueryResult) +export class AISQLQueryResolver { + constructor( + private readonly aiSqlQueryService: AISQLQueryService, + @InjectRepository(FeatureFlagEntity, 'core') + private readonly featureFlagRepository: Repository, + ) {} + + @Query(() => AISQLQueryResult) + async getAISQLQuery( + @AuthWorkspace() { id: workspaceId }: Workspace, + @AuthUser() user: User, + @Args() { text }: GetAISQLQueryArgs, + ) { + const isCopilotEnabledFeatureFlag = + await this.featureFlagRepository.findOneBy({ + workspaceId, + key: FeatureFlagKeys.IsCopilotEnabled, + value: true, + }); + + if (!isCopilotEnabledFeatureFlag?.value) { + throw new ForbiddenException( + `${FeatureFlagKeys.IsCopilotEnabled} feature flag is disabled`, + ); + } + + const traceMetadata = { + userId: user.id, + userEmail: user.email, + }; + + return this.aiSqlQueryService.generateAndExecute( + workspaceId, + text, + traceMetadata, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.service.ts b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.service.ts new file mode 100644 index 0000000000..b1c9398472 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/ai-sql-query/ai-sql-query.service.ts @@ -0,0 +1,253 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { RunnableSequence } from '@langchain/core/runnables'; +import { StructuredOutputParser } from '@langchain/core/output_parsers'; +import { DataSource, QueryFailedError } from 'typeorm'; +import { z } from 'zod'; +import { zodToJsonSchema } from 'zod-to-json-schema'; +import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions'; +import groupBy from 'lodash.groupby'; + +import { PartialFieldMetadata } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/partial-field-metadata.interface'; + +import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; +import { WorkspaceQueryRunnerService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.service'; +import { LLMChatModelService } from 'src/engine/integrations/llm-chat-model/llm-chat-model.service'; +import { LLMTracingService } from 'src/engine/integrations/llm-tracing/llm-tracing.service'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/object-metadata.constants'; +import { StandardObjectFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-object.factory'; +import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects'; +import { AISQLQueryResult } from 'src/engine/core-modules/ai-sql-query/dtos/ai-sql-query-result.dto'; +import { sqlGenerationPromptTemplate } from 'src/engine/core-modules/ai-sql-query/ai-sql-query.prompt-templates'; + +@Injectable() +export class AISQLQueryService { + private readonly logger = new Logger(AISQLQueryService.name); + constructor( + private readonly workspaceDataSourceService: WorkspaceDataSourceService, + private readonly workspaceQueryRunnerService: WorkspaceQueryRunnerService, + private readonly llmChatModelService: LLMChatModelService, + private readonly llmTracingService: LLMTracingService, + private readonly standardObjectFactory: StandardObjectFactory, + ) {} + + private getLabelIdentifierName( + objectMetadata: ObjectMetadataEntity, + dataSourceId, + workspaceId, + workspaceFeatureFlagsMap, + ): string | undefined { + const customObjectLabelIdentifierFieldMetadata = objectMetadata.fields.find( + (fieldMetadata) => + fieldMetadata.id === objectMetadata.labelIdentifierFieldMetadataId, + ); + + const standardObjectMetadataCollection = this.standardObjectFactory.create( + standardObjectMetadataDefinitions, + { workspaceId, dataSourceId }, + workspaceFeatureFlagsMap, + ); + + const standardObjectLabelIdentifierFieldMetadata = + standardObjectMetadataCollection + .find( + (standardObjectMetadata) => + standardObjectMetadata.nameSingular === objectMetadata.nameSingular, + ) + ?.fields.find( + (field: PartialFieldMetadata) => + field.name === DEFAULT_LABEL_IDENTIFIER_FIELD_NAME, + ) as PartialFieldMetadata; + + const labelIdentifierFieldMetadata = + customObjectLabelIdentifierFieldMetadata ?? + standardObjectLabelIdentifierFieldMetadata; + + return ( + labelIdentifierFieldMetadata?.name ?? DEFAULT_LABEL_IDENTIFIER_FIELD_NAME + ); + } + + private async getColInfosByTableName(dataSource: DataSource) { + const { schema } = dataSource.options as PostgresConnectionOptions; + + // From LangChain sql_utils.ts + const sqlQuery = `SELECT + t.table_name, + c.* + FROM + information_schema.tables t + JOIN information_schema.columns c + ON t.table_name = c.table_name + WHERE + t.table_schema = '${schema}' + AND c.table_schema = '${schema}' + ORDER BY + t.table_name, + c.ordinal_position;`; + const colInfos = await dataSource.query< + { + table_name: string; + column_name: string; + data_type: string | undefined; + is_nullable: 'YES' | 'NO'; + }[] + >(sqlQuery); + + return groupBy(colInfos, (colInfo) => colInfo.table_name); + } + + private getCreateTableStatement(tableName: string, colInfos: any[]) { + return `${`CREATE TABLE ${tableName} (\n`} ${colInfos + .map( + (colInfo) => + `${colInfo.column_name} ${colInfo.data_type} ${ + colInfo.is_nullable === 'YES' ? '' : 'NOT NULL' + }`, + ) + .join(', ')});`; + } + + private getRelationDescriptions() { + // TODO - Construct sentences like the following: + // investorId: a foreign key referencing the person table, indicating the investor who owns this portfolio company. + return ''; + } + + private getTableDescription(tableName: string, colInfos: any[]) { + return [ + this.getCreateTableStatement(tableName, colInfos), + this.getRelationDescriptions(), + ].join('\n'); + } + + private async getWorkspaceSchemaDescription( + dataSource: DataSource, + ): Promise { + const colInfoByTableName = await this.getColInfosByTableName(dataSource); + + return Object.entries(colInfoByTableName) + .map(([tableName, colInfos]) => + this.getTableDescription(tableName, colInfos), + ) + .join('\n\n'); + } + + private async generateWithDataSource( + workspaceId: string, + workspaceDataSource: DataSource, + userQuestion: string, + traceMetadata: Record = {}, + ) { + const workspaceSchemaName = + this.workspaceDataSourceService.getSchemaName(workspaceId); + + workspaceDataSource.setOptions({ + schema: workspaceSchemaName, + }); + + const workspaceSchemaDescription = + await this.getWorkspaceSchemaDescription(workspaceDataSource); + + const llmOutputSchema = z.object({ + sqlQuery: z.string(), + }); + + const llmOutputJsonSchema = JSON.stringify( + zodToJsonSchema(llmOutputSchema), + ); + + const structuredOutputParser = + StructuredOutputParser.fromZodSchema(llmOutputSchema); + + const sqlQueryGeneratorChain = RunnableSequence.from([ + sqlGenerationPromptTemplate, + this.llmChatModelService.getJSONChatModel(), + structuredOutputParser, + ]); + + const metadata = { + workspaceId, + ...traceMetadata, + }; + const tracingCallbackHandler = + this.llmTracingService.getCallbackHandler(metadata); + + const { sqlQuery } = await sqlQueryGeneratorChain.invoke( + { + llmOutputJsonSchema, + sqlCreateTableStatements: workspaceSchemaDescription, + userQuestion, + }, + { + callbacks: [tracingCallbackHandler], + }, + ); + + return sqlQuery; + } + + async generate( + workspaceId: string, + userQuestion: string, + traceMetadata: Record = {}, + ) { + const workspaceDataSource = + await this.workspaceDataSourceService.connectToWorkspaceDataSource( + workspaceId, + ); + + return this.generateWithDataSource( + workspaceId, + workspaceDataSource, + userQuestion, + traceMetadata, + ); + } + + async generateAndExecute( + workspaceId: string, + userQuestion: string, + traceMetadata: Record = {}, + ): Promise { + const workspaceDataSource = + await this.workspaceDataSourceService.connectToWorkspaceDataSource( + workspaceId, + ); + + const sqlQuery = await this.generateWithDataSource( + workspaceId, + workspaceDataSource, + userQuestion, + traceMetadata, + ); + + try { + const sqlQueryResult: Record[] = + await this.workspaceQueryRunnerService.executeSQL( + workspaceDataSource, + workspaceId, + sqlQuery, + ); + + return { + sqlQuery, + sqlQueryResult: JSON.stringify(sqlQueryResult), + }; + } catch (error) { + if (error instanceof QueryFailedError) { + return { + sqlQuery, + queryFailedErrorMessage: error.message, + }; + } + + this.logger.error(error.message, error.stack); + + return { + sqlQuery, + }; + } + } +} diff --git a/packages/twenty-server/src/engine/core-modules/ai-sql-query/dtos/ai-sql-query-result.dto.ts b/packages/twenty-server/src/engine/core-modules/ai-sql-query/dtos/ai-sql-query-result.dto.ts new file mode 100644 index 0000000000..1046631f32 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/ai-sql-query/dtos/ai-sql-query-result.dto.ts @@ -0,0 +1,17 @@ +import { Field, ObjectType } from '@nestjs/graphql'; + +import { IsOptional } from 'class-validator'; + +@ObjectType('AISQLQueryResult') +export class AISQLQueryResult { + @Field(() => String) + sqlQuery: string; + + @Field(() => String, { nullable: true }) + @IsOptional() + sqlQueryResult?: string; + + @Field(() => String, { nullable: true }) + @IsOptional() + queryFailedErrorMessage?: string; +} diff --git a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts index 2a706f024d..cb63a112cc 100644 --- a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts +++ b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts @@ -10,6 +10,7 @@ import { TimelineMessagingModule } from 'src/engine/core-modules/messaging/timel import { TimelineCalendarEventModule } from 'src/engine/core-modules/calendar/timeline-calendar-event.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { HealthModule } from 'src/engine/core-modules/health/health.module'; +import { AISQLQueryModule } from 'src/engine/core-modules/ai-sql-query/ai-sql-query.module'; import { PostgresCredentialsModule } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.module'; import { AnalyticsModule } from './analytics/analytics.module'; @@ -31,6 +32,7 @@ import { ClientConfigModule } from './client-config/client-config.module'; TimelineCalendarEventModule, UserModule, WorkspaceModule, + AISQLQueryModule, PostgresCredentialsModule, ], exports: [ diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts index 1bf944a66d..29fb9a8c80 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.entity.ts @@ -22,6 +22,7 @@ export enum FeatureFlagKeys { IsPostgreSQLIntegrationEnabled = 'IS_POSTGRESQL_INTEGRATION_ENABLED', IsStripeIntegrationEnabled = 'IS_STRIPE_INTEGRATION_ENABLED', IsContactCreationForSentAndReceivedEmailsEnabled = 'IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED', + IsCopilotEnabled = 'IS_COPILOT_ENABLED', IsMessagingAliasFetchingEnabled = 'IS_MESSAGING_ALIAS_FETCHING_ENABLED', IsGoogleCalendarSyncV2Enabled = 'IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED', IsFreeAccessEnabled = 'IS_FREE_ACCESS_ENABLED', diff --git a/packages/twenty-server/src/engine/integrations/environment/environment-variables.ts b/packages/twenty-server/src/engine/integrations/environment/environment-variables.ts index 423dee3b31..544045b65d 100644 --- a/packages/twenty-server/src/engine/integrations/environment/environment-variables.ts +++ b/packages/twenty-server/src/engine/integrations/environment/environment-variables.ts @@ -17,6 +17,8 @@ import { import { EmailDriver } from 'src/engine/integrations/email/interfaces/email.interface'; import { NodeEnvironment } from 'src/engine/integrations/environment/interfaces/node-environment.interface'; +import { LLMChatModelDriver } from 'src/engine/integrations/llm-chat-model/interfaces/llm-chat-model.interface'; +import { LLMTracingDriver } from 'src/engine/integrations/llm-tracing/interfaces/llm-tracing.interface'; import { assert } from 'src/utils/assert'; import { CastToStringArray } from 'src/engine/integrations/environment/decorators/cast-to-string-array.decorator'; @@ -369,6 +371,16 @@ export class EnvironmentVariables { OPENROUTER_API_KEY: string; + LLM_CHAT_MODEL_DRIVER: LLMChatModelDriver = LLMChatModelDriver.OpenAI; + + OPENAI_API_KEY: string; + + LANGFUSE_SECRET_KEY: string; + + LANGFUSE_PUBLIC_KEY: string; + + LLM_TRACING_DRIVER: LLMTracingDriver = LLMTracingDriver.Console; + @CastToPositiveNumber() API_RATE_LIMITING_TTL = 100; diff --git a/packages/twenty-server/src/engine/integrations/integrations.module.ts b/packages/twenty-server/src/engine/integrations/integrations.module.ts index fff43cdfd2..fb2f40051e 100644 --- a/packages/twenty-server/src/engine/integrations/integrations.module.ts +++ b/packages/twenty-server/src/engine/integrations/integrations.module.ts @@ -12,6 +12,10 @@ import { emailModuleFactory } from 'src/engine/integrations/email/email.module-f import { CacheStorageModule } from 'src/engine/integrations/cache-storage/cache-storage.module'; import { CaptchaModule } from 'src/engine/integrations/captcha/captcha.module'; import { captchaModuleFactory } from 'src/engine/integrations/captcha/captcha.module-factory'; +import { LLMChatModelModule } from 'src/engine/integrations/llm-chat-model/llm-chat-model.module'; +import { llmChatModelModuleFactory } from 'src/engine/integrations/llm-chat-model/llm-chat-model.module-factory'; +import { LLMTracingModule } from 'src/engine/integrations/llm-tracing/llm-tracing.module'; +import { llmTracingModuleFactory } from 'src/engine/integrations/llm-tracing/llm-tracing.module-factory'; import { EnvironmentModule } from './environment/environment.module'; import { EnvironmentService } from './environment/environment.service'; @@ -50,6 +54,14 @@ import { MessageQueueModule } from './message-queue/message-queue.module'; wildcard: true, }), CacheStorageModule, + LLMChatModelModule.forRoot({ + useFactory: llmChatModelModuleFactory, + inject: [EnvironmentService], + }), + LLMTracingModule.forRoot({ + useFactory: llmTracingModuleFactory, + inject: [EnvironmentService], + }), ], exports: [], providers: [], diff --git a/packages/twenty-server/src/engine/integrations/llm-chat-model/drivers/interfaces/llm-prompt-template-driver.interface.ts b/packages/twenty-server/src/engine/integrations/llm-chat-model/drivers/interfaces/llm-prompt-template-driver.interface.ts new file mode 100644 index 0000000000..cef61eccd3 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-chat-model/drivers/interfaces/llm-prompt-template-driver.interface.ts @@ -0,0 +1,5 @@ +import { BaseChatModel } from '@langchain/core/language_models/chat_models'; + +export interface LLMChatModelDriver { + getJSONChatModel(): BaseChatModel; +} diff --git a/packages/twenty-server/src/engine/integrations/llm-chat-model/drivers/openai.driver.ts b/packages/twenty-server/src/engine/integrations/llm-chat-model/drivers/openai.driver.ts new file mode 100644 index 0000000000..652a854ef8 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-chat-model/drivers/openai.driver.ts @@ -0,0 +1,22 @@ +import { BaseChatModel } from '@langchain/core/language_models/chat_models'; +import { ChatOpenAI } from '@langchain/openai'; + +import { LLMChatModelDriver } from 'src/engine/integrations/llm-chat-model/drivers/interfaces/llm-prompt-template-driver.interface'; + +export class OpenAIDriver implements LLMChatModelDriver { + private chatModel: BaseChatModel; + + constructor() { + this.chatModel = new ChatOpenAI({ + model: 'gpt-4o', + }).bind({ + response_format: { + type: 'json_object', + }, + }) as unknown as BaseChatModel; + } + + getJSONChatModel() { + return this.chatModel; + } +} diff --git a/packages/twenty-server/src/engine/integrations/llm-chat-model/interfaces/llm-chat-model.interface.ts b/packages/twenty-server/src/engine/integrations/llm-chat-model/interfaces/llm-chat-model.interface.ts new file mode 100644 index 0000000000..35a731a4d2 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-chat-model/interfaces/llm-chat-model.interface.ts @@ -0,0 +1,14 @@ +import { ModuleMetadata, FactoryProvider } from '@nestjs/common'; + +export enum LLMChatModelDriver { + OpenAI = 'openai', +} + +export interface LLMChatModelModuleOptions { + type: LLMChatModelDriver; +} + +export type LLMChatModelModuleAsyncOptions = { + useFactory: (...args: any[]) => LLMChatModelModuleOptions; +} & Pick & + Pick; diff --git a/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.constants.ts b/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.constants.ts new file mode 100644 index 0000000000..e6c3ea7b0e --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.constants.ts @@ -0,0 +1 @@ +export const LLM_CHAT_MODEL_DRIVER = Symbol('LLM_CHAT_MODEL_DRIVER'); diff --git a/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.module-factory.ts b/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.module-factory.ts new file mode 100644 index 0000000000..b89a7c9838 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.module-factory.ts @@ -0,0 +1,19 @@ +import { LLMChatModelDriver } from 'src/engine/integrations/llm-chat-model/interfaces/llm-chat-model.interface'; + +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; + +export const llmChatModelModuleFactory = ( + environmentService: EnvironmentService, +) => { + const driver = environmentService.get('LLM_CHAT_MODEL_DRIVER'); + + switch (driver) { + case LLMChatModelDriver.OpenAI: { + return { type: LLMChatModelDriver.OpenAI }; + } + default: + throw new Error( + `Invalid LLM chat model driver (${driver}), check your .env file`, + ); + } +}; diff --git a/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.module.ts b/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.module.ts new file mode 100644 index 0000000000..536c7fcba6 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.module.ts @@ -0,0 +1,35 @@ +import { DynamicModule, Global } from '@nestjs/common'; + +import { + LLMChatModelDriver, + LLMChatModelModuleAsyncOptions, +} from 'src/engine/integrations/llm-chat-model/interfaces/llm-chat-model.interface'; + +import { LLM_CHAT_MODEL_DRIVER } from 'src/engine/integrations/llm-chat-model/llm-chat-model.constants'; +import { OpenAIDriver } from 'src/engine/integrations/llm-chat-model/drivers/openai.driver'; +import { LLMChatModelService } from 'src/engine/integrations/llm-chat-model/llm-chat-model.service'; + +@Global() +export class LLMChatModelModule { + static forRoot(options: LLMChatModelModuleAsyncOptions): DynamicModule { + const provider = { + provide: LLM_CHAT_MODEL_DRIVER, + useFactory: (...args: any[]) => { + const config = options.useFactory(...args); + + switch (config.type) { + case LLMChatModelDriver.OpenAI: { + return new OpenAIDriver(); + } + } + }, + inject: options.inject || [], + }; + + return { + module: LLMChatModelModule, + providers: [LLMChatModelService, provider], + exports: [LLMChatModelService], + }; + } +} diff --git a/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.service.ts b/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.service.ts new file mode 100644 index 0000000000..62beea8c6e --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-chat-model/llm-chat-model.service.ts @@ -0,0 +1,16 @@ +import { Injectable, Inject } from '@nestjs/common'; + +import { LLMChatModelDriver } from 'src/engine/integrations/llm-chat-model/drivers/interfaces/llm-prompt-template-driver.interface'; + +import { LLM_CHAT_MODEL_DRIVER } from 'src/engine/integrations/llm-chat-model/llm-chat-model.constants'; + +@Injectable() +export class LLMChatModelService { + constructor( + @Inject(LLM_CHAT_MODEL_DRIVER) private driver: LLMChatModelDriver, + ) {} + + getJSONChatModel() { + return this.driver.getJSONChatModel(); + } +} diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/console.driver.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/console.driver.ts new file mode 100644 index 0000000000..47e126324d --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/console.driver.ts @@ -0,0 +1,25 @@ +import { BaseCallbackHandler } from '@langchain/core/callbacks/base'; +import { ConsoleCallbackHandler } from '@langchain/core/tracers/console'; +import { Run } from '@langchain/core/tracers/base'; + +import { LLMTracingDriver } from 'src/engine/integrations/llm-tracing/drivers/interfaces/llm-tracing-driver.interface'; + +class WithMetadataConsoleCallbackHandler extends ConsoleCallbackHandler { + private metadata: Record; + + constructor(metadata: Record) { + super(); + this.metadata = metadata; + } + + onChainStart(run: Run) { + console.log(`Chain metadata: ${JSON.stringify(this.metadata)}`); + super.onChainStart(run); + } +} + +export class ConsoleDriver implements LLMTracingDriver { + getCallbackHandler(metadata: Record): BaseCallbackHandler { + return new WithMetadataConsoleCallbackHandler(metadata); + } +} diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/interfaces/llm-tracing-driver.interface.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/interfaces/llm-tracing-driver.interface.ts new file mode 100644 index 0000000000..fe1944a60c --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/interfaces/llm-tracing-driver.interface.ts @@ -0,0 +1,5 @@ +import { BaseCallbackHandler } from '@langchain/core/callbacks/base'; + +export interface LLMTracingDriver { + getCallbackHandler(metadata: Record): BaseCallbackHandler; +} diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/langfuse.driver.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/langfuse.driver.ts new file mode 100644 index 0000000000..b9b84aad08 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-tracing/drivers/langfuse.driver.ts @@ -0,0 +1,26 @@ +import { BaseCallbackHandler } from '@langchain/core/callbacks/base'; +import CallbackHandler from 'langfuse-langchain'; + +import { LLMTracingDriver } from 'src/engine/integrations/llm-tracing/drivers/interfaces/llm-tracing-driver.interface'; + +export interface LangfuseDriverOptions { + secretKey: string; + publicKey: string; +} + +export class LangfuseDriver implements LLMTracingDriver { + private options: LangfuseDriverOptions; + + constructor(options: LangfuseDriverOptions) { + this.options = options; + } + + getCallbackHandler(metadata: Record): BaseCallbackHandler { + return new CallbackHandler({ + secretKey: this.options.secretKey, + publicKey: this.options.publicKey, + baseUrl: 'https://cloud.langfuse.com', + metadata: metadata, + }); + } +} diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/interfaces/llm-tracing.interface.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/interfaces/llm-tracing.interface.ts new file mode 100644 index 0000000000..a97031499b --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-tracing/interfaces/llm-tracing.interface.ts @@ -0,0 +1,26 @@ +import { ModuleMetadata, FactoryProvider } from '@nestjs/common'; + +import { LangfuseDriverOptions } from 'src/engine/integrations/llm-tracing/drivers/langfuse.driver'; + +export enum LLMTracingDriver { + Langfuse = 'langfuse', + Console = 'console', +} + +export interface LangfuseDriverFactoryOptions { + type: LLMTracingDriver.Langfuse; + options: LangfuseDriverOptions; +} + +export interface ConsoleDriverFactoryOptions { + type: LLMTracingDriver.Console; +} + +export type LLMTracingModuleOptions = + | LangfuseDriverFactoryOptions + | ConsoleDriverFactoryOptions; + +export type LLMTracingModuleAsyncOptions = { + useFactory: (...args: any[]) => LLMTracingModuleOptions; +} & Pick & + Pick; diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.constants.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.constants.ts new file mode 100644 index 0000000000..92371f0e4e --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.constants.ts @@ -0,0 +1 @@ +export const LLM_TRACING_DRIVER = Symbol('LLM_TRACING_DRIVER'); diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.module-factory.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.module-factory.ts new file mode 100644 index 0000000000..754158e2a8 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.module-factory.ts @@ -0,0 +1,34 @@ +import { LLMTracingDriver } from 'src/engine/integrations/llm-tracing/interfaces/llm-tracing.interface'; + +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; + +export const llmTracingModuleFactory = ( + environmentService: EnvironmentService, +) => { + const driver = environmentService.get('LLM_TRACING_DRIVER'); + + switch (driver) { + case LLMTracingDriver.Console: { + return { type: LLMTracingDriver.Console as const }; + } + case LLMTracingDriver.Langfuse: { + const secretKey = environmentService.get('LANGFUSE_SECRET_KEY'); + const publicKey = environmentService.get('LANGFUSE_PUBLIC_KEY'); + + if (!(secretKey && publicKey)) { + throw new Error( + `${driver} LLM tracing driver requires LANGFUSE_SECRET_KEY and LANGFUSE_PUBLIC_KEY to be defined, check your .env file`, + ); + } + + return { + type: LLMTracingDriver.Langfuse as const, + options: { secretKey, publicKey }, + }; + } + default: + throw new Error( + `Invalid LLM tracing driver (${driver}), check your .env file`, + ); + } +}; diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.module.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.module.ts new file mode 100644 index 0000000000..9e9c452e95 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.module.ts @@ -0,0 +1,39 @@ +import { Global, DynamicModule } from '@nestjs/common'; + +import { + LLMTracingModuleAsyncOptions, + LLMTracingDriver, +} from 'src/engine/integrations/llm-tracing/interfaces/llm-tracing.interface'; + +import { LangfuseDriver } from 'src/engine/integrations/llm-tracing/drivers/langfuse.driver'; +import { ConsoleDriver } from 'src/engine/integrations/llm-tracing/drivers/console.driver'; +import { LLMTracingService } from 'src/engine/integrations/llm-tracing/llm-tracing.service'; +import { LLM_TRACING_DRIVER } from 'src/engine/integrations/llm-tracing/llm-tracing.constants'; + +@Global() +export class LLMTracingModule { + static forRoot(options: LLMTracingModuleAsyncOptions): DynamicModule { + const provider = { + provide: LLM_TRACING_DRIVER, + useFactory: (...args: any[]) => { + const config = options.useFactory(...args); + + switch (config.type) { + case LLMTracingDriver.Langfuse: { + return new LangfuseDriver(config.options); + } + case LLMTracingDriver.Console: { + return new ConsoleDriver(); + } + } + }, + inject: options.inject || [], + }; + + return { + module: LLMTracingModule, + providers: [LLMTracingService, provider], + exports: [LLMTracingService], + }; + } +} diff --git a/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.service.ts b/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.service.ts new file mode 100644 index 0000000000..6ff2023902 --- /dev/null +++ b/packages/twenty-server/src/engine/integrations/llm-tracing/llm-tracing.service.ts @@ -0,0 +1,16 @@ +import { Injectable, Inject } from '@nestjs/common'; + +import { BaseCallbackHandler } from '@langchain/core/callbacks/base'; + +import { LLMTracingDriver } from 'src/engine/integrations/llm-tracing/drivers/interfaces/llm-tracing-driver.interface'; + +import { LLM_TRACING_DRIVER } from 'src/engine/integrations/llm-tracing/llm-tracing.constants'; + +@Injectable() +export class LLMTracingService { + constructor(@Inject(LLM_TRACING_DRIVER) private driver: LLMTracingDriver) {} + + getCallbackHandler(metadata: Record): BaseCallbackHandler { + return this.driver.getCallbackHandler(metadata); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.constants.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.constants.ts new file mode 100644 index 0000000000..856d01f5db --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.constants.ts @@ -0,0 +1 @@ +export const DEFAULT_LABEL_IDENTIFIER_FIELD_NAME = 'name'; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts index e4b5405a00..c3b431285d 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/commands/add-standard-id.command.ts @@ -59,6 +59,7 @@ export class AddStandardIdCommand extends CommandRunner { IS_POSTGRESQL_INTEGRATION_ENABLED: true, IS_STRIPE_INTEGRATION_ENABLED: false, IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true, + IS_COPILOT_ENABLED: false, IS_MESSAGING_ALIAS_FETCHING_ENABLED: true, IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true, IS_FREE_ACCESS_ENABLED: false, @@ -77,6 +78,7 @@ export class AddStandardIdCommand extends CommandRunner { IS_POSTGRESQL_INTEGRATION_ENABLED: true, IS_STRIPE_INTEGRATION_ENABLED: false, IS_CONTACT_CREATION_FOR_SENT_AND_RECEIVED_EMAILS_ENABLED: true, + IS_COPILOT_ENABLED: false, IS_MESSAGING_ALIAS_FETCHING_ENABLED: true, IS_GOOGLE_CALENDAR_SYNC_V2_ENABLED: true, IS_FREE_ACCESS_ENABLED: false, diff --git a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts index 9ff7c5008b..0ba89be2f7 100644 --- a/packages/twenty-ui/src/display/icon/components/TablerIcons.ts +++ b/packages/twenty-ui/src/display/icon/components/TablerIcons.ts @@ -33,6 +33,7 @@ export { IconCalendarEvent, IconCalendarTime, IconCalendarX, + IconChartCandle, IconCheck, IconCheckbox, IconChevronDown, @@ -137,10 +138,13 @@ export { IconReload, IconRepeat, IconRocket, + IconRotate, IconSearch, IconSend, IconSettings, IconSortDescending, + IconSparkles, + IconSql, IconSquareRoundedCheck, IconTable, IconTag, diff --git a/packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx b/packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx index 7d50e1a083..4badf15e3c 100644 --- a/packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx +++ b/packages/twenty-website/src/content/developers/self-hosting/self-hosting-var.mdx @@ -170,8 +170,13 @@ yarn command:prod cron:calendar:google-calendar-sync ### Data enrichment and AI + ['OPENROUTER_API_KEY', '', "The API key for openrouter.ai, an abstraction layer over models from Mistral, OpenAI and more"], + ['OPENAI_API_KEY', 'sk-proj-abcdabcd...', "OpenAI API key"], + ['LLM_CHAT_MODEL_DRIVER', 'openai', "LLM provider"], + ['LLM_TRACING_DRIVER', 'langfuse', "Where to output LangChain logs. 'langfuse' or 'console'."], + ['LANGFUSE_SECRET_KEY', 'sk-lf-abcdabcd-abcd...', "Langfuse secret key"], + ['LANGFUSE_PUBLIC_KEY', 'pk-lf-abcdabcd-abcd...', "Langfuse public key"], +]}> ### Support Chat diff --git a/yarn.lock b/yarn.lock index 5188200cab..fa1c6a0889 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7695,6 +7695,95 @@ __metadata: languageName: node linkType: hard +"@langchain/core@npm:>0.1.56 <0.3.0, @langchain/core@npm:>=0.2.5 <0.3.0": + version: 0.2.6 + resolution: "@langchain/core@npm:0.2.6" + dependencies: + ansi-styles: "npm:^5.0.0" + camelcase: "npm:6" + decamelize: "npm:1.2.0" + js-tiktoken: "npm:^1.0.12" + langsmith: "npm:~0.1.30" + ml-distance: "npm:^4.0.0" + mustache: "npm:^4.2.0" + p-queue: "npm:^6.6.2" + p-retry: "npm:4" + uuid: "npm:^9.0.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.3" + checksum: 4ae46528854f9dfc9e6e91c9351275cecc5cb4d246c8a2ae1326fbe046bb8637e5e26743e1ebd86f06f2ef12dc1741ea3f4480701be1e89ceecc5146b8c873b1 + languageName: node + linkType: hard + +"@langchain/core@npm:>0.2.0 <0.3.0, @langchain/core@npm:>=0.2.8 <0.3.0, @langchain/core@npm:~0.2.0": + version: 0.2.9 + resolution: "@langchain/core@npm:0.2.9" + dependencies: + ansi-styles: "npm:^5.0.0" + camelcase: "npm:6" + decamelize: "npm:1.2.0" + js-tiktoken: "npm:^1.0.12" + langsmith: "npm:~0.1.30" + ml-distance: "npm:^4.0.0" + mustache: "npm:^4.2.0" + p-queue: "npm:^6.6.2" + p-retry: "npm:4" + uuid: "npm:^9.0.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.3" + checksum: e336b2c90d4955cc522f3295b1f2b09e89b88d483108ca89af20c88ee41f815b91a307a193d0359e5012fb348022dcafa43a7ed28a195ae74ef4ec6a59678e4d + languageName: node + linkType: hard + +"@langchain/mistralai@npm:^0.0.24": + version: 0.0.24 + resolution: "@langchain/mistralai@npm:0.0.24" + dependencies: + "@langchain/core": "npm:>0.1.56 <0.3.0" + "@mistralai/mistralai": "npm:^0.4.0" + uuid: "npm:^9.0.0" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.4" + checksum: abbc862685dfa48e9f4418ff94843b38d779514f46d5a971dc0c98b55a44d6fe91f3610432ccc0af39496ef075fbffd3e514e4104af5968938e0270e1a07716d + languageName: node + linkType: hard + +"@langchain/openai@npm:>=0.1.0 <0.3.0": + version: 0.2.0 + resolution: "@langchain/openai@npm:0.2.0" + dependencies: + "@langchain/core": "npm:>=0.2.8 <0.3.0" + js-tiktoken: "npm:^1.0.12" + openai: "npm:^4.49.1" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.3" + checksum: a55cf7f42f4df901049b98e592bde9ee3e4a027635b07697110f511d2ccb4118ea562cd9c9d0de0e43efe291ed18425d6f6dea47447b27ba35e756c9b969fba0 + languageName: node + linkType: hard + +"@langchain/openai@npm:^0.1.3": + version: 0.1.3 + resolution: "@langchain/openai@npm:0.1.3" + dependencies: + "@langchain/core": "npm:>=0.2.5 <0.3.0" + js-tiktoken: "npm:^1.0.12" + openai: "npm:^4.49.1" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.3" + checksum: b693bd9d5ec118136f99279c50b531865702d0fa41479cde4ced05bb20b487cc75656761e491b380e69ff191b38b8a41e4573a485b4da42f57061adb7aa692eb + languageName: node + linkType: hard + +"@langchain/textsplitters@npm:~0.0.0": + version: 0.0.3 + resolution: "@langchain/textsplitters@npm:0.0.3" + dependencies: + "@langchain/core": "npm:>0.2.0 <0.3.0" + js-tiktoken: "npm:^1.0.12" + checksum: 3297b48f636a8a6acbd65f1465624741e6d557512ea8020a208cc6b2aa6e8d752cd08511a92ef980a06ed95858b7750a1126a4e6acfbb75fd4733e050651f405 + languageName: node + linkType: hard + "@leichtgewicht/ip-codec@npm:^2.0.1": version: 2.0.4 resolution: "@leichtgewicht/ip-codec@npm:2.0.4" @@ -7991,6 +8080,15 @@ __metadata: languageName: node linkType: hard +"@mistralai/mistralai@npm:^0.4.0": + version: 0.4.0 + resolution: "@mistralai/mistralai@npm:0.4.0" + dependencies: + node-fetch: "npm:^2.6.7" + checksum: 1857ceb56f9119e8248ebb3947f8ee1da6e0731aeced38e1de44823448376ddc733182dd782e50a0e448ddd6b01789bd9dc622e0594e7a354af302a06ced77e7 + languageName: node + linkType: hard + "@mole-inc/bin-wrapper@npm:^8.0.1": version: 8.0.1 resolution: "@mole-inc/bin-wrapper@npm:8.0.1" @@ -17632,6 +17730,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^18.11.18": + version: 18.19.34 + resolution: "@types/node@npm:18.19.34" + dependencies: + undici-types: "npm:~5.26.4" + checksum: e985f50684def801801069e236165ee511f9195fc04ad4a2af7642d86aeaeaf7bfe34c147f894a48618a5c71c15b388ca91341a244792149543a712e38351988 + languageName: node + linkType: hard + "@types/nodemailer@npm:^6.4.14": version: 6.4.14 resolution: "@types/nodemailer@npm:6.4.14" @@ -19189,6 +19296,15 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: "npm:^5.0.0" + checksum: 90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 + languageName: node + linkType: hard + "accepts@npm:^1.3.5, accepts@npm:~1.3.4, accepts@npm:~1.3.5, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" @@ -21513,7 +21629,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.0.2, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": +"base64-js@npm:^1.0.2, base64-js@npm:^1.3.0, base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf @@ -21662,6 +21778,20 @@ __metadata: languageName: node linkType: hard +"binary-extensions@npm:^2.2.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 + languageName: node + linkType: hard + +"binary-search@npm:^1.3.5": + version: 1.3.6 + resolution: "binary-search@npm:1.3.6" + checksum: 786a770e3411cf563c9c7829e2854d79583a207b8faaa5022f93352893e1d06035ae5d80de1b168dcbd9d346fdb0dd2e3d7fcdf309b3a63dc027e92624da32a0 + languageName: node + linkType: hard + "binaryextensions@npm:^4.15.0, binaryextensions@npm:^4.16.0": version: 4.19.0 resolution: "binaryextensions@npm:4.19.0" @@ -22494,6 +22624,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:6, camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + "camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": version: 5.3.1 resolution: "camelcase@npm:5.3.1" @@ -22501,13 +22638,6 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^6.2.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - "camelcase@npm:^7.0.1": version: 7.0.1 resolution: "camelcase@npm:7.0.1" @@ -23577,7 +23707,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:^10.0.0": +"commander@npm:^10.0.0, commander@npm:^10.0.1": version: 10.0.1 resolution: "commander@npm:10.0.1" checksum: 53f33d8927758a911094adadda4b2cbac111a5b377d8706700587650fd8f45b0bbe336de4b5c3fe47fd61f420a3d9bd452b6e0e6e5600a7e74d7bf0174f6efe3 @@ -25100,7 +25230,7 @@ __metadata: languageName: node linkType: hard -"decamelize@npm:^1.2.0": +"decamelize@npm:1.2.0, decamelize@npm:^1.2.0": version: 1.2.0 resolution: "decamelize@npm:1.2.0" checksum: 85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 @@ -27557,6 +27687,13 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b + languageName: node + linkType: hard + "eventemitter2@npm:6.4.9": version: 6.4.9 resolution: "eventemitter2@npm:6.4.9" @@ -28657,6 +28794,13 @@ __metadata: languageName: node linkType: hard +"form-data-encoder@npm:1.7.2": + version: 1.7.2 + resolution: "form-data-encoder@npm:1.7.2" + checksum: 56553768037b6d55d9de524f97fe70555f0e415e781cb56fc457a68263de3d40fadea2304d4beef2d40b1a851269bd7854e42c362107071892cb5238debe9464 + languageName: node + linkType: hard + "form-data-encoder@npm:^2.1.2": version: 2.1.4 resolution: "form-data-encoder@npm:2.1.4" @@ -28693,7 +28837,7 @@ __metadata: languageName: node linkType: hard -"formdata-node@npm:^4.4.1": +"formdata-node@npm:^4.3.2, formdata-node@npm:^4.4.1": version: 4.4.1 resolution: "formdata-node@npm:4.4.1" dependencies: @@ -31807,6 +31951,13 @@ __metadata: languageName: node linkType: hard +"is-any-array@npm:^2.0.0": + version: 2.0.1 + resolution: "is-any-array@npm:2.0.1" + checksum: f9807458a51e63ca1ac27fd6f3a3ace8200f077094e00d9b05b24cfbc9d5594d586d6ecf3416271f26939d5cb93fc52ca869cb5744e77318c3f53ec70b08d61f + languageName: node + linkType: hard + "is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1": version: 1.1.1 resolution: "is-arguments@npm:1.1.1" @@ -33556,6 +33707,15 @@ __metadata: languageName: node linkType: hard +"js-tiktoken@npm:^1.0.12": + version: 1.0.12 + resolution: "js-tiktoken@npm:1.0.12" + dependencies: + base64-js: "npm:^1.5.1" + checksum: 7afb4826e21342386a1884754fbc1c1828f948c4dd0ab093bf778d1323e65343bd5343d15f7cda46af396f1fe4a0297739936149b7c40a0601eefe3fcaef8727 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -34032,7 +34192,7 @@ __metadata: languageName: node linkType: hard -"jsonpointer@npm:^5.0.0": +"jsonpointer@npm:^5.0.0, jsonpointer@npm:^5.0.1": version: 5.0.1 resolution: "jsonpointer@npm:5.0.1" checksum: 89929e58b400fcb96928c0504fcf4fc3f919d81e9543ceb055df125538470ee25290bb4984251e172e6ef8fcc55761eb998c118da763a82051ad89d4cb073fe7 @@ -34232,6 +34392,242 @@ __metadata: languageName: node linkType: hard +"langchain@npm:^0.2.6": + version: 0.2.6 + resolution: "langchain@npm:0.2.6" + dependencies: + "@langchain/core": "npm:~0.2.0" + "@langchain/openai": "npm:>=0.1.0 <0.3.0" + "@langchain/textsplitters": "npm:~0.0.0" + binary-extensions: "npm:^2.2.0" + js-tiktoken: "npm:^1.0.12" + js-yaml: "npm:^4.1.0" + jsonpointer: "npm:^5.0.1" + langchainhub: "npm:~0.0.8" + langsmith: "npm:~0.1.30" + ml-distance: "npm:^4.0.0" + openapi-types: "npm:^12.1.3" + p-retry: "npm:4" + uuid: "npm:^9.0.0" + yaml: "npm:^2.2.1" + zod: "npm:^3.22.4" + zod-to-json-schema: "npm:^3.22.3" + peerDependencies: + "@aws-sdk/client-s3": ^3.310.0 + "@aws-sdk/client-sagemaker-runtime": ^3.310.0 + "@aws-sdk/client-sfn": ^3.310.0 + "@aws-sdk/credential-provider-node": ^3.388.0 + "@azure/storage-blob": ^12.15.0 + "@browserbasehq/sdk": "*" + "@gomomento/sdk": ^1.51.1 + "@gomomento/sdk-core": ^1.51.1 + "@gomomento/sdk-web": ^1.51.1 + "@mendable/firecrawl-js": ^0.0.13 + "@notionhq/client": ^2.2.10 + "@pinecone-database/pinecone": "*" + "@supabase/supabase-js": ^2.10.0 + "@vercel/kv": ^0.2.3 + "@xata.io/client": ^0.28.0 + apify-client: ^2.7.1 + assemblyai: ^4.0.0 + axios: "*" + cheerio: ^1.0.0-rc.12 + chromadb: "*" + convex: ^1.3.1 + couchbase: ^4.3.0 + d3-dsv: ^2.0.0 + epub2: ^3.0.1 + fast-xml-parser: "*" + handlebars: ^4.7.8 + html-to-text: ^9.0.5 + ignore: ^5.2.0 + ioredis: ^5.3.2 + jsdom: "*" + mammoth: ^1.6.0 + mongodb: ">=5.2.0" + node-llama-cpp: "*" + notion-to-md: ^3.1.0 + officeparser: ^4.0.4 + pdf-parse: 1.1.1 + peggy: ^3.0.2 + playwright: ^1.32.1 + puppeteer: ^19.7.2 + pyodide: ^0.24.1 + redis: ^4.6.4 + sonix-speech-recognition: ^2.1.1 + srt-parser-2: ^1.2.3 + typeorm: ^0.3.20 + weaviate-ts-client: "*" + web-auth-library: ^1.0.3 + ws: ^8.14.2 + youtube-transcript: ^1.0.6 + youtubei.js: ^9.1.0 + peerDependenciesMeta: + "@aws-sdk/client-s3": + optional: true + "@aws-sdk/client-sagemaker-runtime": + optional: true + "@aws-sdk/client-sfn": + optional: true + "@aws-sdk/credential-provider-node": + optional: true + "@azure/storage-blob": + optional: true + "@browserbasehq/sdk": + optional: true + "@gomomento/sdk": + optional: true + "@gomomento/sdk-core": + optional: true + "@gomomento/sdk-web": + optional: true + "@mendable/firecrawl-js": + optional: true + "@notionhq/client": + optional: true + "@pinecone-database/pinecone": + optional: true + "@supabase/supabase-js": + optional: true + "@vercel/kv": + optional: true + "@xata.io/client": + optional: true + apify-client: + optional: true + assemblyai: + optional: true + axios: + optional: true + cheerio: + optional: true + chromadb: + optional: true + convex: + optional: true + couchbase: + optional: true + d3-dsv: + optional: true + epub2: + optional: true + faiss-node: + optional: true + fast-xml-parser: + optional: true + handlebars: + optional: true + html-to-text: + optional: true + ignore: + optional: true + ioredis: + optional: true + jsdom: + optional: true + mammoth: + optional: true + mongodb: + optional: true + node-llama-cpp: + optional: true + notion-to-md: + optional: true + officeparser: + optional: true + pdf-parse: + optional: true + peggy: + optional: true + playwright: + optional: true + puppeteer: + optional: true + pyodide: + optional: true + redis: + optional: true + sonix-speech-recognition: + optional: true + srt-parser-2: + optional: true + typeorm: + optional: true + weaviate-ts-client: + optional: true + web-auth-library: + optional: true + ws: + optional: true + youtube-transcript: + optional: true + youtubei.js: + optional: true + checksum: c267d618f20b75eeba0c13b3ee9aaa8e7a57d87d64344c4360d7332bdda82c6976c80c705a81a0be02006f350b2f42142ca98a7b71148ee0aa934a592ddbc47a + languageName: node + linkType: hard + +"langchainhub@npm:~0.0.8": + version: 0.0.11 + resolution: "langchainhub@npm:0.0.11" + checksum: 6ed781b9e8165bfb5cedc822a25bc70df0f3fc02662061d19a5e2044243cfae797857a05d139de8f326539b1f3fe03f2662060eed82669e405181f1f0f435c47 + languageName: node + linkType: hard + +"langfuse-core@npm:^3.11.2": + version: 3.11.2 + resolution: "langfuse-core@npm:3.11.2" + dependencies: + mustache: "npm:^4.2.0" + checksum: 341cddedf16cf0c4b980989c4ee4daa6be0dad7a37349d86ff7b3fcf4e2e35e25318fb9ec6e4c9d27023031c7e780d50d75493f7cf9911938b6581ee2c1728c6 + languageName: node + linkType: hard + +"langfuse-langchain@npm:^3.11.2": + version: 3.11.2 + resolution: "langfuse-langchain@npm:3.11.2" + dependencies: + langfuse: "npm:^3.11.2" + langfuse-core: "npm:^3.11.2" + peerDependencies: + langchain: ">=0.0.157 <0.3.0" + checksum: ec57481128b4b738ee4e146b0f0c42e94d0f75d5a525a031407d7ecd8ffcd50dc9730f580901ed2432694555e544459e032e9181af9d853a156aef42c3ef9b41 + languageName: node + linkType: hard + +"langfuse@npm:^3.11.2": + version: 3.11.2 + resolution: "langfuse@npm:3.11.2" + dependencies: + langfuse-core: "npm:^3.11.2" + checksum: e74053aa5bb3e62a91d3c7a5f95c86f9808342ca9a0dcda5c5a33bf8abf8ace70357f84db7fae36e93d722e9aa383b55ced785e6d6cbf23d47d0252b1fef57b0 + languageName: node + linkType: hard + +"langsmith@npm:~0.1.30": + version: 0.1.30 + resolution: "langsmith@npm:0.1.30" + dependencies: + "@types/uuid": "npm:^9.0.1" + commander: "npm:^10.0.1" + p-queue: "npm:^6.6.2" + p-retry: "npm:4" + uuid: "npm:^9.0.0" + peerDependencies: + "@langchain/core": "*" + langchain: "*" + openai: "*" + peerDependenciesMeta: + "@langchain/core": + optional: true + langchain: + optional: true + openai: + optional: true + checksum: 181719d73bd89918f0ab60768f824449e2bfd5a691242c3540291114053ba6e49ce4670bfa0abd129dd7556f80b3025371ba9cd1884090dc1941fdf39850545c + languageName: node + linkType: hard + "language-subtag-registry@npm:^0.3.20": version: 0.3.22 resolution: "language-subtag-registry@npm:0.3.22" @@ -37622,6 +38018,52 @@ __metadata: languageName: node linkType: hard +"ml-array-mean@npm:^1.1.6": + version: 1.1.6 + resolution: "ml-array-mean@npm:1.1.6" + dependencies: + ml-array-sum: "npm:^1.1.6" + checksum: 41ab68308e3472702f775a49c8ab9ee1e678e01cd59dbc59424c0f1017a37df1bb638e702831305f0e6366300eca48353f526773ab8f4d8d142a64d0461f9944 + languageName: node + linkType: hard + +"ml-array-sum@npm:^1.1.6": + version: 1.1.6 + resolution: "ml-array-sum@npm:1.1.6" + dependencies: + is-any-array: "npm:^2.0.0" + checksum: fb3973ce2bfa19ab4f5e657f722494425b57025547657d332bf5bafe3e4e7e4bd1e082dfb970bcc0bfa87efa345b80a20a596dbb204be7b4802b52c35fd6cf77 + languageName: node + linkType: hard + +"ml-distance-euclidean@npm:^2.0.0": + version: 2.0.0 + resolution: "ml-distance-euclidean@npm:2.0.0" + checksum: 877aef472e134f79be9540b02f889b2a27976ca45d77f5d4ef7d8dd24058a60cf4637365b40a5aba1ab5490348a0fb1b3803143b25af88cdc66137fbfd665442 + languageName: node + linkType: hard + +"ml-distance@npm:^4.0.0": + version: 4.0.1 + resolution: "ml-distance@npm:4.0.1" + dependencies: + ml-array-mean: "npm:^1.1.6" + ml-distance-euclidean: "npm:^2.0.0" + ml-tree-similarity: "npm:^1.0.0" + checksum: 8c2eb077d2ba61437f2414f3b9ca1091c43fcabe2282ecc31d8ebf9e083c1df068e43c67f59a4674e7c8f473201ace4f02779b446427d6169a5d669cae94c816 + languageName: node + linkType: hard + +"ml-tree-similarity@npm:^1.0.0": + version: 1.0.0 + resolution: "ml-tree-similarity@npm:1.0.0" + dependencies: + binary-search: "npm:^1.3.5" + num-sort: "npm:^2.0.0" + checksum: e3ecd07bead5d18bc7b6fed1dfefbe65aea4008d5556181b94b7d70550fba543d2501b224f12a9f5197c1d23d95faef2accc7fd265c5afd15ef55a38190ffc6e + languageName: node + linkType: hard + "mlly@npm:^1.2.0, mlly@npm:^1.4.2": version: 1.6.1 resolution: "mlly@npm:1.6.1" @@ -37848,6 +38290,15 @@ __metadata: languageName: node linkType: hard +"mustache@npm:^4.2.0": + version: 4.2.0 + resolution: "mustache@npm:4.2.0" + bin: + mustache: bin/mustache + checksum: 1f8197e8a19e63645a786581d58c41df7853da26702dbc005193e2437c98ca49b255345c173d50c08fe4b4dbb363e53cb655ecc570791f8deb09887248dd34a2 + languageName: node + linkType: hard + "mute-stream@npm:0.0.8": version: 0.0.8 resolution: "mute-stream@npm:0.0.8" @@ -38715,6 +39166,13 @@ __metadata: languageName: node linkType: hard +"num-sort@npm:^2.0.0": + version: 2.1.0 + resolution: "num-sort@npm:2.1.0" + checksum: cc1d43adbc9adfd5d208a8eb653827277376ff2e6eb75379f96e6a23d481040e317e63505e075b84ce49e19b9d960570646096428a715d12c5ef1381504d5135 + languageName: node + linkType: hard + "number-is-nan@npm:^1.0.0": version: 1.0.1 resolution: "number-is-nan@npm:1.0.1" @@ -39107,6 +39565,24 @@ __metadata: languageName: node linkType: hard +"openai@npm:^4.49.1": + version: 4.51.0 + resolution: "openai@npm:4.51.0" + dependencies: + "@types/node": "npm:^18.11.18" + "@types/node-fetch": "npm:^2.6.4" + abort-controller: "npm:^3.0.0" + agentkeepalive: "npm:^4.2.1" + form-data-encoder: "npm:1.7.2" + formdata-node: "npm:^4.3.2" + node-fetch: "npm:^2.6.7" + web-streams-polyfill: "npm:^3.2.1" + bin: + openai: bin/cli + checksum: c9adcca092aa528fe3556d9e91e022f67515e76c31ee6899d781b5bf17fbfd783e7aca15da4b6dca4bed53d12da021973789f5ae584d2769c5c560976939c45b + languageName: node + linkType: hard + "openapi-types@npm:^12.1.3": version: 12.1.3 resolution: "openapi-types@npm:12.1.3" @@ -39421,7 +39897,7 @@ __metadata: languageName: node linkType: hard -"p-retry@npm:^4.5.0": +"p-retry@npm:4, p-retry@npm:^4.5.0": version: 4.6.2 resolution: "p-retry@npm:4.6.2" dependencies: @@ -47183,6 +47659,8 @@ __metadata: resolution: "twenty-server@workspace:packages/twenty-server" dependencies: "@graphql-yoga/nestjs": "patch:@graphql-yoga/nestjs@2.1.0#./patches/@graphql-yoga-nestjs-npm-2.1.0-cb509e6047.patch" + "@langchain/mistralai": "npm:^0.0.24" + "@langchain/openai": "npm:^0.1.3" "@nestjs/cache-manager": "npm:^2.2.1" "@nestjs/cli": "npm:10.3.0" "@nestjs/devtools-integration": "npm:^0.1.6" @@ -47206,6 +47684,8 @@ __metadata: graphql-middleware: "npm:^6.1.35" jsdom: "npm:~22.1.0" jwt-decode: "npm:^4.0.0" + langchain: "npm:^0.2.6" + langfuse-langchain: "npm:^3.11.2" lodash.differencewith: "npm:^4.5.0" lodash.omitby: "npm:^4.6.0" lodash.uniq: "npm:^4.5.0" @@ -47215,6 +47695,7 @@ __metadata: rimraf: "npm:^5.0.5" tsconfig-paths: "npm:^4.2.0" typescript: "npm:5.3.3" + zod-to-json-schema: "npm:^3.23.1" languageName: unknown linkType: soft @@ -50324,6 +50805,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.2.1": + version: 2.4.5 + resolution: "yaml@npm:2.4.5" + bin: + yaml: bin.mjs + checksum: e1ee78b381e5c710f715cc4082fd10fc82f7f5c92bd6f075771d20559e175616f56abf1c411f545ea0e9e16e4f84a83a50b42764af5f16ec006328ba9476bb31 + languageName: node + linkType: hard + "yaml@npm:^2.2.2, yaml@npm:^2.3.4": version: 2.3.4 resolution: "yaml@npm:2.3.4" @@ -50656,7 +51146,25 @@ __metadata: languageName: node linkType: hard -"zod@npm:3.23.8": +"zod-to-json-schema@npm:^3.22.3, zod-to-json-schema@npm:^3.22.4": + version: 3.23.0 + resolution: "zod-to-json-schema@npm:3.23.0" + peerDependencies: + zod: ^3.23.3 + checksum: bcd966fa040765d7170a89c0c5f1717575e7d8823b84cbbb606689d494ae308c9eaadd4b71a74752e3170deef64c1f1bb2985f4663c44a0ed2e7854ff6fda724 + languageName: node + linkType: hard + +"zod-to-json-schema@npm:^3.23.1": + version: 3.23.1 + resolution: "zod-to-json-schema@npm:3.23.1" + peerDependencies: + zod: ^3.23.3 + checksum: d48d733f7cba9fdc631ebe3dada3f48b820a16e49f7ded9f363cccafa42461ff95cc7afcf974c27af7cd6d5fa5191212bb7ec15ec203bcb61f829a6d0d3e192f + languageName: node + linkType: hard + +"zod@npm:3.23.8, zod@npm:^3.22.4": version: 3.23.8 resolution: "zod@npm:3.23.8" checksum: 8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69