mirror of
https://github.com/marcominerva/SqlDatabaseVectorSearch.git
synced 2026-06-20 12:23:10 +00:00
Enhance ChatService with new prompts and simplifications
Updated ChatService to include static readonly fields for reformulation and answering prompts. Replaced the existing ChatSystemPrompt in CreateQuestionAsync. Simplified GetTokenUsage using expression-bodied members. Modified SetChatHistoryAsync to ensure correct chat history updates in the cache.
This commit is contained in:
@@ -15,19 +15,81 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
|
||||
{
|
||||
private readonly AppSettings appSettings = appSettingsOptions.Value;
|
||||
|
||||
private static readonly string systemPromptForReformulation = """
|
||||
You are a helpful assistant that reformulates questions to perform embeddings search.
|
||||
Your task is to reformulate the question taking into account the context of the chat.
|
||||
The reformulated question must always explicitly contain the subject of the question.
|
||||
You must reformulate the question in the same language of the user's question. For example, if the user asks a question in English, the answer must be in English.
|
||||
Never add "in this chat", "in the context of this chat", "in the context of our conversation", "search for" or something like that in your answer.
|
||||
""";
|
||||
|
||||
private static readonly string systemPromptForAnswering = """
|
||||
You can use only the information provided in this chat to answer questions. If you don't know the answer, reply suggesting to refine the question.
|
||||
|
||||
For example, if the user asks "What is the capital of Italy?" and in this chat there isn't information about Italy, you should reply something like:
|
||||
- This information isn't available in the given context.
|
||||
- I'm sorry, I don't know the answer to that question.
|
||||
- I don't have that information.
|
||||
- I don't know.
|
||||
- Given the context, I can't answer that question.
|
||||
- I'm sorry, I don't have enough information to answer that question.
|
||||
|
||||
Never answer questions that are not related to this chat.
|
||||
You must answer in the same language as the user's question. For example, if the user asks a question in English, the answer must be in English, no matter the language of the documents.
|
||||
|
||||
FORMATTING REQUIREMENT: Your answer MUST ALWAYS end with a period followed by a space before the citations block.
|
||||
If your answer doesn't naturally end with a period, you MUST add one followed by a space.
|
||||
|
||||
After the answer, you need to include citations following the XML format below ONLY IF you know the answer and are providing information from the context. If you do NOT know the answer, DO NOT include the citations section at all.
|
||||
|
||||
【<citation document-id="document_id" chunk-id="chunk_id" filename="string" page-number="page_number" index-on-page="index_on-page">exact quote here</citation>
|
||||
<citation document-id="document_id" chunk-id="chunk_id" filename="string" page-number="page_number" index-on-page="index_on-page">exact quote here</citation>】
|
||||
|
||||
The entire list of XML citations MUST be enclosed between 【 and 】 (U+3010 and U+3011) and must exactly match the above format.
|
||||
The quote in each <citation> MUST be MAXIMUM 5 words, taken word-for-word from the search result.
|
||||
|
||||
IMPORTANT CITATION RULES:
|
||||
1. NEVER put citations inside your answer text.
|
||||
2. ALWAYS provide your complete answer FIRST.
|
||||
3. ONLY AFTER completing your answer, add ALL citations in a block at the very end.
|
||||
4. The citations block MUST be the last thing in your response, with absolutely nothing (no text, no spaces, no newlines, no punctuation, no comments) after it.
|
||||
5. NEVER reference citations by number or mention them in your answer text.
|
||||
6. The citations MUST ALWAYS follow the XML format exactly as shown below. Any other format is NOT ACCEPTED.
|
||||
7. If you add anything after the citations block, your answer will be considered invalid.
|
||||
8. If you do NOT know the answer, DO NOT include the citations block at all.
|
||||
9. ALWAYS check that your answer ends with a period followed by a space before adding citations.
|
||||
|
||||
---
|
||||
Example of a correct answer:
|
||||
The capital of Italy is Rome.
|
||||
【<citation document-id="123" chunk-id="456" filename="italy.pdf" page-number="1" index-on-page="1">capital of Italy is Rome</citation>】
|
||||
|
||||
Example of a correct answer when you do NOT know the answer:
|
||||
I'm sorry, I don't know the answer to that question.
|
||||
|
||||
Example of an incorrect answer (NOT ACCEPTED):
|
||||
The capital of Italy is Rome
|
||||
【<citation document-id="123" chunk-id="456" filename="italy.pdf" page-number="1" index-on-page="1">capital of Italy is Rome</citation>】
|
||||
Thank you for your question.
|
||||
|
||||
Another incorrect example (NOT ACCEPTED):
|
||||
The capital of Italy is Rome.
|
||||
【<citation document-id="123" chunk-id="456" filename="italy.pdf" page-number="1" index-on-page="1">capital of Italy is Rome</citation>】
|
||||
[1] italy.pdf, page 1
|
||||
---
|
||||
|
||||
Only the correct format is accepted. If you do not follow the XML format exactly, or if you add anything after the citations block, your answer will be considered invalid.
|
||||
If you do NOT know the answer, DO NOT include the citations block at all.
|
||||
Remember to ALWAYS end your answer with a period followed by a space before adding citations.
|
||||
""";
|
||||
|
||||
public async Task<ChatResponse> CreateQuestionAsync(Guid conversationId, string question, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var chat = await GetChatHistoryAsync(conversationId, cancellationToken);
|
||||
|
||||
var settings = new AzureOpenAIPromptExecutionSettings
|
||||
{
|
||||
ChatSystemPrompt = """
|
||||
You are a helpful assistant that reformulates questions to perform embeddings search.
|
||||
Your task is to reformulate the question taking into account the context of the chat.
|
||||
The reformulated question must always explicitly contain the subject of the question.
|
||||
You must reformulate the question in the same language of the user's question. For example, if the user asks a question in English, the answer must be in English.
|
||||
Never add "in this chat", "in the context of this chat", "in the context of our conversation", "search for" or something like that in your answer.
|
||||
"""
|
||||
ChatSystemPrompt = systemPromptForReformulation
|
||||
};
|
||||
|
||||
var embeddingQuestion = $"""
|
||||
@@ -39,6 +101,7 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
|
||||
chat.AddUserMessage(embeddingQuestion);
|
||||
|
||||
var reformulatedQuestion = await chatCompletionService.GetChatMessageContentAsync(chat, settings, cancellationToken: cancellationToken);
|
||||
|
||||
chat.AddAssistantMessage(reformulatedQuestion.Content!);
|
||||
|
||||
await UpdateCacheAsync(conversationId, chat, cancellationToken);
|
||||
@@ -89,28 +152,16 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
|
||||
}
|
||||
|
||||
// Add question and answer to the chat history.
|
||||
await SetChatHistoryAsync(conversationId, question, answer.ToString(), cancellationToken);
|
||||
await SetChatHistoryAsync(conversationId, question, answer.ToString(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static TokenUsage? GetTokenUsage(Microsoft.SemanticKernel.ChatMessageContent message)
|
||||
{
|
||||
if (message.InnerContent is ChatCompletion content && content.Usage is not null)
|
||||
{
|
||||
return new(content.Usage.InputTokenCount, content.Usage.OutputTokenCount);
|
||||
}
|
||||
private static TokenUsage? GetTokenUsage(Microsoft.SemanticKernel.ChatMessageContent message) =>
|
||||
message.InnerContent is ChatCompletion content && content.Usage is not null
|
||||
? new(content.Usage.InputTokenCount, content.Usage.OutputTokenCount) : null;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static TokenUsage? GetTokenUsage(Microsoft.SemanticKernel.StreamingChatMessageContent message)
|
||||
{
|
||||
if (message.InnerContent is StreamingChatCompletionUpdate content && content.Usage is not null)
|
||||
{
|
||||
return new(content.Usage.InputTokenCount, content.Usage.OutputTokenCount);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
private static TokenUsage? GetTokenUsage(Microsoft.SemanticKernel.StreamingChatMessageContent message) =>
|
||||
message.InnerContent is StreamingChatCompletionUpdate content && content.Usage is not null
|
||||
? new(content.Usage.InputTokenCount, content.Usage.OutputTokenCount) : null;
|
||||
|
||||
private (ChatHistory Chat, AzureOpenAIPromptExecutionSettings Settings) CreateChatAsync(IEnumerable<Entities.DocumentChunk> chunks, string question)
|
||||
{
|
||||
@@ -119,65 +170,7 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
|
||||
MaxTokens = appSettings.MaxOutputTokens
|
||||
};
|
||||
|
||||
var chat = new ChatHistory("""
|
||||
You can use only the information provided in this chat to answer questions. If you don't know the answer, reply suggesting to refine the question.
|
||||
|
||||
For example, if the user asks "What is the capital of Italy?" and in this chat there isn't information about Italy, you should reply something like:
|
||||
- This information isn't available in the given context.
|
||||
- I'm sorry, I don't know the answer to that question.
|
||||
- I don't have that information.
|
||||
- I don't know.
|
||||
- Given the context, I can't answer that question.
|
||||
- I'm sorry, I don't have enough information to answer that question.
|
||||
|
||||
Never answer questions that are not related to this chat.
|
||||
You must answer in the same language as the user's question. For example, if the user asks a question in English, the answer must be in English, no matter the language of the documents.
|
||||
|
||||
FORMATTING REQUIREMENT: Your answer MUST ALWAYS end with a period followed by a space before the citations block.
|
||||
If your answer doesn't naturally end with a period, you MUST add one followed by a space.
|
||||
|
||||
After the answer, you need to include citations following the XML format below ONLY IF you know the answer and are providing information from the context. If you do NOT know the answer, DO NOT include the citations section at all.
|
||||
|
||||
【<citation document-id="document_id" chunk-id="chunk_id" filename="string" page-number="page_number" index-on-page="index_on-page">exact quote here</citation>
|
||||
<citation document-id="document_id" chunk-id="chunk_id" filename="string" page-number="page_number" index-on-page="index_on-page">exact quote here</citation>】
|
||||
|
||||
The entire list of XML citations MUST be enclosed between 【 and 】 (U+3010 and U+3011) and must exactly match the above format.
|
||||
The quote in each <citation> MUST be MAXIMUM 5 words, taken word-for-word from the search result.
|
||||
|
||||
IMPORTANT CITATION RULES:
|
||||
1. NEVER put citations inside your answer text.
|
||||
2. ALWAYS provide your complete answer FIRST.
|
||||
3. ONLY AFTER completing your answer, add ALL citations in a block at the very end.
|
||||
4. The citations block MUST be the last thing in your response, with absolutely nothing (no text, no spaces, no newlines, no punctuation, no comments) after it.
|
||||
5. NEVER reference citations by number or mention them in your answer text.
|
||||
6. The citations MUST ALWAYS follow the XML format exactly as shown below. Any other format is NOT ACCEPTED.
|
||||
7. If you add anything after the citations block, your answer will be considered invalid.
|
||||
8. If you do NOT know the answer, DO NOT include the citations block at all.
|
||||
9. ALWAYS check that your answer ends with a period followed by a space before adding citations.
|
||||
|
||||
---
|
||||
Example of a correct answer:
|
||||
The capital of Italy is Rome.
|
||||
【<citation document-id="123" chunk-id="456" filename="italy.pdf" page-number="1" index-on-page="1">capital of Italy is Rome</citation>】
|
||||
|
||||
Example of a correct answer when you do NOT know the answer:
|
||||
I'm sorry, I don't know the answer to that question.
|
||||
|
||||
Example of an incorrect answer (NOT ACCEPTED):
|
||||
The capital of Italy is Rome
|
||||
【<citation document-id="123" chunk-id="456" filename="italy.pdf" page-number="1" index-on-page="1">capital of Italy is Rome</citation>】
|
||||
Thank you for your question.
|
||||
|
||||
Another incorrect example (NOT ACCEPTED):
|
||||
The capital of Italy is Rome.
|
||||
【<citation document-id="123" chunk-id="456" filename="italy.pdf" page-number="1" index-on-page="1">capital of Italy is Rome</citation>】
|
||||
[1] italy.pdf, page 1
|
||||
---
|
||||
|
||||
Only the correct format is accepted. If you do not follow the XML format exactly, or if you add anything after the citations block, your answer will be considered invalid.
|
||||
If you do NOT know the answer, DO NOT include the citations block at all.
|
||||
Remember to ALWAYS end your answer with a period followed by a space before adding citations.
|
||||
""");
|
||||
var chat = new ChatHistory(systemPromptForAnswering);
|
||||
|
||||
var prompt = new StringBuilder($"""
|
||||
Answer the following question:
|
||||
@@ -189,7 +182,7 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
|
||||
""");
|
||||
|
||||
var availableTokens = appSettings.MaxInputTokens
|
||||
- tokenizerService.CountChatCompletionTokens(chat[0].ToString()) // System prompt.
|
||||
- tokenizerService.CountChatCompletionTokens(systemPromptForAnswering) // System prompt.
|
||||
- tokenizerService.CountChatCompletionTokens(prompt.ToString()) // Initial user prompt.
|
||||
- appSettings.MaxOutputTokens; // To ensure there is enough space for the answer.
|
||||
|
||||
@@ -215,6 +208,7 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
|
||||
}
|
||||
|
||||
chat.AddUserMessage(prompt.ToString());
|
||||
|
||||
return (chat, settings);
|
||||
}
|
||||
|
||||
@@ -230,22 +224,21 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
|
||||
|
||||
private async Task<ChatHistory> GetChatHistoryAsync(Guid conversationId, CancellationToken cancellationToken)
|
||||
{
|
||||
var historyCache = await cache.GetOrCreateAsync(conversationId.ToString(), (cancellationToken) =>
|
||||
var chat = await cache.GetOrCreateAsync(conversationId.ToString(), (cancellationToken) =>
|
||||
{
|
||||
return ValueTask.FromResult<ChatHistory>([]);
|
||||
}, cancellationToken: cancellationToken);
|
||||
|
||||
var chat = new ChatHistory(historyCache);
|
||||
return chat;
|
||||
}
|
||||
|
||||
private async Task SetChatHistoryAsync(Guid conversationId, string question, string answer, CancellationToken cancellationToken)
|
||||
{
|
||||
var history = await GetChatHistoryAsync(conversationId, cancellationToken);
|
||||
var chat = await GetChatHistoryAsync(conversationId, cancellationToken);
|
||||
|
||||
history.AddUserMessage(question);
|
||||
history.AddAssistantMessage(answer);
|
||||
chat.AddUserMessage(question);
|
||||
chat.AddAssistantMessage(answer);
|
||||
|
||||
await UpdateCacheAsync(conversationId, history, cancellationToken);
|
||||
await UpdateCacheAsync(conversationId, chat, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user