Skip to content

Commit 9df21a6

Browse files
committed
fix(ai-bot): better prompt template typing
1 parent 4df204b commit 9df21a6

13 files changed

+164
-82
lines changed

packages/@liexp/backend/src/flows/ai/createEventFromDocuments.flow.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ export const createEventFromDocuments = <
2222
>(
2323
content: Document[],
2424
type: EventType,
25-
prompt: PromptFn,
25+
prompt: PromptFn<{
26+
type: EventType;
27+
jsonSchema: string;
28+
question: string;
29+
context: string;
30+
}>,
2631
jsonSchema: unknown,
2732
question: string | null,
2833
): ReaderTaskEither<C, APIError, Event> => {

packages/@liexp/backend/src/flows/ai/createEventFromText.flow.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,26 @@ import { runRagChain } from "./runRagChain.js";
1919

2020
export const getCreateEventPromptPartial =
2121
<C extends LoggerContext>(
22-
promptTemplate: PromptFn,
22+
promptTemplate: PromptFn<{
23+
type: EventType;
24+
jsonSchema: string;
25+
context: string;
26+
question: string;
27+
}>,
2328
type: EventType,
2429
jsonSchema: any,
2530
): ReaderTaskEither<C, APIError, PromptTemplate> =>
2631
(ctx) => {
2732
return fp.TE.tryCatch(async () => {
2833
const prompt = await PromptTemplate.fromTemplate(
29-
promptTemplate({ type, jsonSchema }),
34+
promptTemplate({
35+
vars: {
36+
type,
37+
jsonSchema,
38+
question: "{question}",
39+
context: "{context}",
40+
},
41+
}),
3042
).partial({
3143
evenType: type,
3244
jsonSchema: JSON.stringify(jsonSchema),
@@ -46,7 +58,12 @@ export const getCreateEventPromptPartial =
4658
export const createEventFromText = <C extends LoggerContext & LangchainContext>(
4759
text: Document[],
4860
type: EventType,
49-
promptTemplate: PromptFn,
61+
promptTemplate: PromptFn<{
62+
jsonSchema: string;
63+
type: EventType;
64+
context: string;
65+
question: string;
66+
}>,
5067
jsonSchema: string,
5168
question: string,
5269
): ReaderTaskEither<C, APIError | DBError, Event> => {

packages/@liexp/backend/src/providers/ai/langchain.provider.ts

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,49 +7,61 @@ import {
77
} from "@langchain/core/runnables";
88
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
99
import { GetLogger } from "@liexp/core/lib/logger/index.js";
10+
import { type PromptFn } from "@liexp/shared/lib/io/openai/prompts/prompt.type.js";
1011
import type * as Reader from "fp-ts/lib/Reader.js";
1112
import { loadSummarizationChain } from "langchain/chains";
1213
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
1314
import { formatDocumentsAsString } from "langchain/util/document";
1415
import { MemoryVectorStore } from "langchain/vectorstores/memory";
1516

16-
export const EMBEDDINGS_PROMPT = `You are an assistant for question-answering tasks.
17+
export const EMBEDDINGS_PROMPT: PromptFn<{
18+
text: string;
19+
question: string;
20+
}> = ({
21+
vars: { text, question },
22+
}) => `You are an assistant for question-answering tasks.
1723
Use the following pieces of retrieved context to answer the question.
1824
If you don't know the answer, just say that you don't know.
1925
Use 300 chars maximum and keep the answers concise.
2026
2127
---
22-
{context}
28+
${text}
2329
---
2430
25-
Question: {question}
31+
Question: ${question}
2632
2733
Answer:
2834
`;
2935

3036
// const DEFAULT_SUMMARIZATION_QUESTION = "Can you summarize this article for me?";
3137

32-
export const DEFAULT_SUMMARIZE_PROMPT = `
38+
export const DEFAULT_SUMMARIZE_PROMPT: PromptFn<{ text: string }> = ({
39+
vars: { text },
40+
}) => `
3341
You are an expert in summarizing texts. These texts can be either excerpt of web pages or articles.
3442
Your goal is to create a summary of the given text, focusing on the actions made by the characters mentioned in the text.
3543
Below you find the text you need to summarize.
3644
3745
--------
38-
{text}
46+
${text}
3947
--------
4048
`;
4149

4250
export interface LangchainProvider {
4351
chat: ChatOpenAI;
4452
embeddings: OpenAIEmbeddings;
45-
queryDocument: (
53+
queryDocument: <Args extends { text: string; question?: string }>(
4654
url: LangchainDocument[],
4755
question: string,
48-
options?: { model?: AvailableModels; prompt?: string },
56+
options?: { model?: AvailableModels; prompt?: PromptFn<Args> },
4957
) => Promise<string>;
50-
summarizeText: (
58+
summarizeText: <Args extends { text: string }>(
5159
text: LangchainDocument[],
52-
options?: { model?: AvailableModels; prompt?: string; question?: string },
60+
options?: {
61+
model?: AvailableModels;
62+
prompt?: PromptFn<Args>;
63+
question?: string;
64+
},
5365
) => Promise<string>;
5466
}
5567

@@ -144,11 +156,13 @@ export const GetLangchainProvider = (
144156
// Retrieve and generate using the relevant snippets of the blog.
145157
const retriever = vectorStore.asRetriever();
146158

147-
const prompt = PromptTemplate.fromTemplate(EMBEDDINGS_PROMPT);
159+
const prompt = PromptTemplate.fromTemplate(
160+
EMBEDDINGS_PROMPT({ vars: { question: "{question}", text: "{text}" } }),
161+
);
148162

149163
const ragChain = RunnableSequence.from([
150164
{
151-
context: retriever.pipe(formatDocumentsAsString),
165+
text: retriever.pipe(formatDocumentsAsString),
152166
question: new RunnablePassthrough(),
153167
},
154168
prompt,
@@ -165,7 +179,14 @@ export const GetLangchainProvider = (
165179

166180
return output;
167181
},
168-
summarizeText: async (text, options) => {
182+
summarizeText: async <Args extends { text: string }>(
183+
text: LangchainDocument[],
184+
options?: {
185+
model?: AvailableModels;
186+
prompt?: PromptFn<Args>;
187+
question?: string;
188+
},
189+
) => {
169190
const model = options?.model ?? opts.models?.chat ?? "gpt-4o";
170191
const prompt = options?.prompt ?? DEFAULT_SUMMARIZE_PROMPT;
171192

@@ -185,7 +206,13 @@ export const GetLangchainProvider = (
185206
});
186207
const docsSummary = await textSplitter.splitDocuments(text);
187208

188-
const SUMMARY_PROMPT = PromptTemplate.fromTemplate(prompt);
209+
const SUMMARY_PROMPT = PromptTemplate.fromTemplate(
210+
prompt({
211+
vars: {
212+
text: docsSummary.flatMap((doc) => doc.pageContent).join("\n"),
213+
} as Args,
214+
}),
215+
);
189216

190217
const summarizeChain = loadSummarizationChain(chat, {
191218
type: "stuff",
Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
import { type PromptFn } from "./prompt.type.js";
22

3-
export const EMBED_ACTOR_PROMPT: PromptFn = () => `
3+
export const EMBED_ACTOR_PROMPT: PromptFn<{ text: string }> = ({
4+
vars: { text },
5+
}) => `
46
You are an expert in giving description about people.
5-
Your goal is to give a description of this person in a text format, including the requested fields, without inventing details.
7+
Your goal is to give a description of a given person in a text format, including the requested fields, without inventing details.
68
The text should be minimum 100 words long, but not exceeding 300 words long.
79
810
The requested fields are:
911
- name: the name of the person
10-
- birthOn: the birth date of the person in the format "YYYY-MM-DD"
11-
- diedOn: the death date of the person in the format "YYYY-MM-DD"
12+
- birthOn: the birth date of the person (in the format "YYYY-MM-DD")
13+
- diedOn: the death date of the person (in the format "YYYY-MM-DD")
1214
- excerpt: a short description of the person (leave 2 empty lines at the end of this block)
1315
- body: a longer description of the person
1416
15-
----
16-
{text}
17-
----
17+
Here's the name of the person you need to describe:
18+
19+
---------------------------------------------------------------
20+
${text}
21+
---------------------------------------------------------------
1822
`;
1923

20-
export const ACTOR_GENERAL_INFO_PROMPT: PromptFn = () => `
24+
export const ACTOR_GENERAL_INFO_PROMPT: PromptFn<{
25+
text: string;
26+
question: string;
27+
}> = ({ vars }) => {
28+
const { text, question } = vars;
29+
return `
2130
You are an expert in giving description about people.
2231
Your goal is to create a description including the requested fields about a given person, without inventing details.
2332
@@ -30,11 +39,12 @@ The requested fields are:
3039
3140
Below you find the person name and, optionally, a previous description of the person.
3241
33-
--------
34-
{text}
35-
--------
42+
---------------------------------------------------------------
43+
${text}
44+
---------------------------------------------------------------
3645
37-
Question: {question}
46+
Question: ${question}
3847
39-
Answer:
48+
Give me your answer.
4049
`;
50+
};
Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,63 @@
1-
import { EventType } from "../../http/Events/EventType.js";
1+
import { type EventType } from "../../http/Events/EventType.js";
22
import { type PromptFn } from "./prompt.type.js";
33

4-
export const EMBED_EVENT_PROMPT = () => `
4+
export const EMBED_EVENT_PROMPT: PromptFn<{ text: string }> = ({ vars }) => `
55
You are an expert in summarizing texts. These texts can be either excerpt of web pages or articles.
66
Your goal is to create a summary of the given text, focusing on the actions made by the characters mentioned in the text.
77
Try to extract the date of the event, the groups and actors involved.
8+
Respond with a professional summary of the event, without starting with "the text is about" or similar expression.
89
9-
Below you find the text you need to summarize.
10-
11-
--------
12-
{text}
13-
--------
10+
---------------------------------------------------------------
11+
${vars.text}
12+
---------------------------------------------------------------
1413
`;
1514

16-
const CREATE_EVENT_PROMPT = () => `
15+
const CREATE_EVENT_PROMPT: PromptFn<{
16+
jsonSchema: string;
17+
type: EventType;
18+
}> = ({ vars }) => `
1719
You are an expert in summarizing texts. These texts can be either excerpt of web pages or articles.
1820
Your goal is to create a summary of the given text, focusing on the actions made by the characters mentioned in given context.
1921
Try to match the dates with the given fields, using ISO 8601 format.
20-
The event can be one of the following type: ${EventType.types.map((t) => t.value).join(", ")}.
22+
The event type is the following: "${vars.type}".
2123
2224
You return the summarized text in the "excerpt" key of the json object, and adapt others information to the following JSON OPENAPI schema:
2325
24-
---
25-
{jsonSchema}
26-
---
26+
---------------------------------------------------------------
27+
${vars.jsonSchema}
28+
---------------------------------------------------------------
29+
2730
`;
2831

2932
/**
3033
* Prompt for creating an event from text.
3134
*
32-
* @param format_instructions Instructions on how to format the event.
33-
* @param text The text to extract the event from.
3435
*/
35-
export const CREATE_EVENT_FROM_URL_PROMPT = () => `
36-
${CREATE_EVENT_PROMPT()}
36+
export const CREATE_EVENT_FROM_URL_PROMPT: PromptFn<{
37+
jsonSchema: string;
38+
context: string;
39+
type: EventType;
40+
}> = ({ vars }) => `
41+
${CREATE_EVENT_PROMPT({ vars })}
3742
3843
The context you need to extract the event from is:
3944
40-
--------
41-
{context}
42-
--------
45+
---------------------------------------------------------------
46+
${vars.context}
47+
---------------------------------------------------------------
4348
`;
4449

4550
/**
46-
* Prompt for creating an event from a question.
51+
* Prompt for creating an event from a text.
4752
*
48-
* @param eventType The type of event to create.
49-
* @param question The question to create the event from.
5053
*/
51-
export const CREATE_EVENT_FROM_TEXT_PROMPT: PromptFn = () => `
52-
${CREATE_EVENT_FROM_URL_PROMPT()}
53-
54-
Try to create a valid event, without inventing any detail from the following question: {question}
54+
export const CREATE_EVENT_FROM_TEXT_PROMPT: PromptFn<{
55+
jsonSchema: string;
56+
context: string;
57+
type: EventType;
58+
question: string;
59+
}> = ({ vars }) => `
60+
${CREATE_EVENT_FROM_URL_PROMPT({ vars })}
61+
62+
Try to create a valid event, without inventing any detail from the following question: ${vars.question}
5563
`;

packages/@liexp/shared/src/io/openai/prompts/group.prompts.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { type PromptFn } from "./prompt.type.js";
22

3-
export const EMBED_GROUP_SUMMARIZE_PROMPT: PromptFn = () => `
3+
export const EMBED_GROUP_SUMMARIZE_PROMPT: PromptFn<{ text: string }> = ({
4+
vars: { text },
5+
}) => `
46
You are an expert in giving description about group.
57
The group can be either a company or a website entity, a group of people, a family group.
68
@@ -16,8 +18,9 @@ The requested fields are:
1618
1719
The text should be maximum 200 words long.
1820
19-
----
20-
{text}
21-
----
21+
Here's the text for the group:
2222
23+
---------------------------------------------------------------
24+
${text}
25+
---------------------------------------------------------------
2326
`;
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { type PromptFn } from "./prompt.type.js";
22

3-
export const EMBED_LINK_PROMPT: PromptFn = () => `
3+
export const EMBED_LINK_PROMPT: PromptFn<{ text: string }> = ({
4+
vars: { text },
5+
}) => `
46
You are an expert in summarizing texts. These texts can be either excerpt of web pages or articles.
5-
Your goal is to create a summary of the given text, focusing on the actions made by the characters mentioned in the text.
7+
Your goal is to create a summary of the given text, focusing on the actions made by the characters mentioned in the text and rephrasing using an investigative tone.
68
Below you find the text you need to summarize.
79
8-
--------
9-
{text}
10-
--------
10+
---------------------------------------------------------------
11+
${text}
12+
---------------------------------------------------------------
1113
`;
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export type PromptFn = (...args: any[]) => string;
1+
export type PromptFn<Vars = undefined> = Vars extends undefined
2+
? () => string
3+
: (args: { vars: Vars }) => string;

services/ai-bot/src/flows/ai/createEventFromText.flow.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { type CreateEventFromTextTypeData } from "@liexp/shared/lib/io/http/Queu
55
import { toAIBotError } from "../../common/error/index.js";
66
import { type ClientContext } from "../../context.js";
77
import { loadDocs } from "./common/loadDocs.flow.js";
8-
import { getPromptFromResource } from "./prompts.js";
8+
import { getEventFromJsonPrompt } from "./prompts.js";
99
import { type JobProcessRTE } from "#services/job-processor/job-processor.service.js";
1010

1111
export const createEventFromTextFlow: JobProcessRTE<
@@ -30,7 +30,7 @@ export const createEventFromTextFlow: JobProcessRTE<
3030
if (job.prompt) {
3131
return fp.RTE.right(() => job.prompt!);
3232
}
33-
return fp.RTE.right(getPromptFromResource(job.resource, job.type));
33+
return fp.RTE.right(getEventFromJsonPrompt(job.type));
3434
}),
3535
fp.RTE.chain(({ docs, jsonSchema, prompt }) =>
3636
createEventFromText<ClientContext>(

0 commit comments

Comments
 (0)