Skip to content

Commit 0bae52d

Browse files
committed
Add AI mode selection and prompt logic
Introduces an AI mode setting to both backend and frontend, allowing users to choose between 'Data Only', 'OpenAI Only', or 'Data + OpenAI' for search and answer generation. Backend approaches now handle the ai_mode override, updating prompt instructions and bypassing document search for OpenAI-only mode. Frontend includes a new AIModeSettings component, updates settings and API models, and passes the selected mode to backend requests. Also updates prompt templates to conditionally include AI mode instructions. Infrastructure defaults for GPT models are updated to use gpt-4o and new deployment versions.
1 parent 5bd86dc commit 0bae52d

17 files changed

+327
-30
lines changed

app/backend/approaches/chatreadretrieveread.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,21 @@ async def run_until_final_call(
9292
else:
9393
extra_info = await self.run_search_approach(messages, overrides, auth_claims)
9494

95+
# Get ai_mode for system prompt context
96+
ai_mode = overrides.get("ai_mode", "data_and_openai")
97+
ai_mode_instructions = {
98+
"data_only": "IMPORTANT: Only use information from the provided documents. Do not use external knowledge or general information that isn't contained in the source documents.",
99+
"openai_only": "Use your general knowledge and reasoning capabilities. No document search results are provided.",
100+
"data_and_openai": "Use both the provided documents and your general knowledge to provide comprehensive answers."
101+
}
102+
103+
prompt_variables = self.get_system_prompt_variables(overrides.get("prompt_template"))
104+
if ai_mode in ai_mode_instructions:
105+
prompt_variables["ai_mode_instruction"] = ai_mode_instructions[ai_mode]
106+
95107
messages = self.prompt_manager.render_prompt(
96108
self.answer_prompt,
97-
self.get_system_prompt_variables(overrides.get("prompt_template"))
109+
prompt_variables
98110
| {
99111
"include_follow_up_questions": bool(overrides.get("suggest_followup_questions")),
100112
"past_messages": messages[:-1],
@@ -129,6 +141,21 @@ async def run_until_final_call(
129141
async def run_search_approach(
130142
self, messages: list[ChatCompletionMessageParam], overrides: dict[str, Any], auth_claims: dict[str, Any]
131143
):
144+
ai_mode = overrides.get("ai_mode", "data_and_openai")
145+
146+
# If OpenAI only mode, skip search entirely and use only OpenAI knowledge
147+
if ai_mode == "openai_only":
148+
return ExtraInfo(
149+
data_points=DataPoints(text=[], images=[]),
150+
thoughts=[
151+
ThoughtStep(
152+
title="AI Mode: OpenAI Only",
153+
description="Using OpenAI's built-in knowledge without searching documents",
154+
props={}
155+
)
156+
]
157+
)
158+
132159
use_text_search = overrides.get("retrieval_mode") in ["text", "hybrid", None]
133160
use_vector_search = overrides.get("retrieval_mode") in ["vectors", "hybrid", None]
134161
use_semantic_ranker = True if overrides.get("semantic_ranker") else False

app/backend/approaches/chatreadretrievereadvision.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,21 @@ async def run_until_final_call(
8181
should_stream: bool = False,
8282
) -> tuple[ExtraInfo, Union[Awaitable[ChatCompletion], Awaitable[AsyncStream[ChatCompletionChunk]]]]:
8383
seed = overrides.get("seed", None)
84+
ai_mode = overrides.get("ai_mode", "data_and_openai")
85+
86+
# If OpenAI only mode, skip search entirely and use only OpenAI knowledge
87+
if ai_mode == "openai_only":
88+
return ExtraInfo(
89+
data_points=DataPoints(text=[], images=[]),
90+
thoughts=[
91+
ThoughtStep(
92+
title="AI Mode: OpenAI Only",
93+
description="Using OpenAI's built-in knowledge without searching documents",
94+
props={}
95+
)
96+
]
97+
)
98+
8499
use_text_search = overrides.get("retrieval_mode") in ["text", "hybrid", None]
85100
use_vector_search = overrides.get("retrieval_mode") in ["vectors", "hybrid", None]
86101
use_semantic_ranker = True if overrides.get("semantic_ranker") else False

app/backend/approaches/prompts/ask_answer_question.prompty

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ If you cannot answer using the sources below, say you don't know. Use below exam
2222
{{ injected_prompt }}
2323
{% endif %}
2424

25+
{% if ai_mode_instruction %}
26+
{{ ai_mode_instruction }}
27+
{% endif %}
28+
2529
user:
2630
What is the deductible for the employee plan for a visit to Overlake in Bellevue?
2731

app/backend/approaches/prompts/chat_answer_question.prompty

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,23 @@ system:
2020
{% if override_prompt %}
2121
{{ override_prompt }}
2222
{% else %}
23+
{% if ai_mode_instruction %}
24+
{{ ai_mode_instruction }}
25+
{% else %}
2326
Assistant helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers.
2427
Answer ONLY with the facts listed in the list of sources below. If there isn't enough information below, say you don't know. Do not generate answers that don't use the sources below. If asking a clarifying question to the user would help, ask the question.
28+
{% endif %}
2529
If the question is not in English, answer in the language used in the question.
30+
{% if text_sources and text_sources|length > 0 %}
2631
Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don't combine sources, list each source separately, for example [info1.txt][info2.pdf].
32+
{% endif %}
2733
{{ injected_prompt }}
2834
{% endif %}
2935

36+
{% if ai_mode_instruction %}
37+
{{ ai_mode_instruction }}
38+
{% endif %}
39+
3040
{% if include_follow_up_questions %}
3141
Generate 3 very brief follow-up questions that the user would likely ask next.
3242
Enclose the follow-up questions in double angle brackets. Example:
@@ -45,7 +55,9 @@ Make sure the last question ends with ">>".
4555
user:
4656
{{ user_query }}
4757

58+
{% if text_sources and text_sources|length > 0 %}
4859
Sources:
4960
{% for text_source in text_sources %}
5061
{{ text_source }}
5162
{% endfor %}
63+
{% endif %}

app/backend/approaches/retrievethenread.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,22 @@ async def run(
8282
else:
8383
extra_info = await self.run_search_approach(messages, overrides, auth_claims)
8484

85+
# Get ai_mode for system prompt context
86+
ai_mode = overrides.get("ai_mode", "data_and_openai")
87+
ai_mode_instructions = {
88+
"data_only": "IMPORTANT: Only use information from the provided documents. Do not use external knowledge or general information that isn't contained in the source documents.",
89+
"openai_only": "Use your general knowledge and reasoning capabilities. No document search results are provided.",
90+
"data_and_openai": "Use both the provided documents and your general knowledge to provide comprehensive answers."
91+
}
92+
93+
prompt_variables = self.get_system_prompt_variables(overrides.get("prompt_template"))
94+
if ai_mode in ai_mode_instructions:
95+
prompt_variables["ai_mode_instruction"] = ai_mode_instructions[ai_mode]
96+
8597
# Process results
8698
messages = self.prompt_manager.render_prompt(
8799
self.answer_prompt,
88-
self.get_system_prompt_variables(overrides.get("prompt_template"))
89-
| {"user_query": q, "text_sources": extra_info.data_points.text},
100+
prompt_variables | {"user_query": q, "text_sources": extra_info.data_points.text},
90101
)
91102

92103
chat_completion = cast(
@@ -121,6 +132,21 @@ async def run(
121132
async def run_search_approach(
122133
self, messages: list[ChatCompletionMessageParam], overrides: dict[str, Any], auth_claims: dict[str, Any]
123134
):
135+
ai_mode = overrides.get("ai_mode", "data_and_openai")
136+
137+
# If OpenAI only mode, skip search entirely and use only OpenAI knowledge
138+
if ai_mode == "openai_only":
139+
return ExtraInfo(
140+
data_points=DataPoints(text=[], images=[]),
141+
thoughts=[
142+
ThoughtStep(
143+
title="AI Mode: OpenAI Only",
144+
description="Using OpenAI's built-in knowledge without searching documents",
145+
props={}
146+
)
147+
]
148+
)
149+
124150
use_text_search = overrides.get("retrieval_mode") in ["text", "hybrid", None]
125151
use_vector_search = overrides.get("retrieval_mode") in ["vectors", "hybrid", None]
126152
use_semantic_ranker = True if overrides.get("semantic_ranker") else False

app/backend/approaches/retrievethenreadvision.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ async def run(
7676
overrides = context.get("overrides", {})
7777
seed = overrides.get("seed", None)
7878
auth_claims = context.get("auth_claims", {})
79+
ai_mode = overrides.get("ai_mode", "data_and_openai")
80+
81+
# If OpenAI only mode, skip search entirely and use only OpenAI knowledge
82+
if ai_mode == "openai_only":
83+
return ExtraInfo(
84+
data_points=DataPoints(text=[], images=[]),
85+
thoughts=[
86+
ThoughtStep(
87+
title="AI Mode: OpenAI Only",
88+
description="Using OpenAI's built-in knowledge without searching documents",
89+
props={}
90+
)
91+
]
92+
)
93+
7994
use_text_search = overrides.get("retrieval_mode") in ["text", "hybrid", None]
8095
use_vector_search = overrides.get("retrieval_mode") in ["vectors", "hybrid", None]
8196
use_semantic_ranker = True if overrides.get("semantic_ranker") else False

app/frontend/src/api/models.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ export const enum RetrievalMode {
44
Text = "text"
55
}
66

7+
export const enum AIMode {
8+
DataOnly = "data_only",
9+
OpenAIOnly = "openai_only",
10+
DataAndOpenAI = "data_and_openai"
11+
}
12+
713
export const enum GPT4VInput {
814
TextAndImages = "textAndImages",
915
Images = "images",
@@ -18,6 +24,7 @@ export const enum VectorFields {
1824

1925
export type ChatAppRequestOverrides = {
2026
retrieval_mode?: RetrievalMode;
27+
ai_mode?: AIMode;
2128
semantic_ranker?: boolean;
2229
semantic_captions?: boolean;
2330
query_rewriting?: boolean;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
.container {
2+
margin-top: 0.625em;
3+
}
4+
5+
.optionSlider {
6+
width: 100%;
7+
max-width: 600px;
8+
margin: 20px auto;
9+
position: relative;
10+
user-select: none;
11+
}
12+
13+
.sliderTrack {
14+
width: 100%;
15+
height: 40px;
16+
background: #e0e0e0;
17+
border-radius: 20px;
18+
position: relative;
19+
}
20+
21+
.sliderThumb {
22+
position: absolute;
23+
top: 0;
24+
width: 33.33%;
25+
height: 40px;
26+
background: #0078d4;
27+
border-radius: 20px;
28+
color: #fff;
29+
text-align: center;
30+
line-height: 40px;
31+
transition: left 0.3s ease;
32+
cursor: pointer;
33+
font-weight: bold;
34+
font-size: 12px;
35+
z-index: 2;
36+
}
37+
38+
.sliderOptions {
39+
display: flex;
40+
justify-content: space-between;
41+
position: absolute;
42+
top: 0;
43+
width: 100%;
44+
height: 40px;
45+
}
46+
47+
.sliderOption {
48+
width: 33.33%;
49+
text-align: center;
50+
line-height: 40px;
51+
color: #666;
52+
cursor: pointer;
53+
font-size: 12px;
54+
font-weight: 500;
55+
z-index: 1;
56+
transition: color 0.3s ease;
57+
}
58+
59+
.sliderOption:hover {
60+
color: #0078d4;
61+
}
62+
63+
.sliderOption.active {
64+
font-weight: bold;
65+
color: #fff;
66+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { useState } from "react";
2+
import { Stack } from "@fluentui/react";
3+
import { useId } from "@fluentui/react-hooks";
4+
import { useTranslation } from "react-i18next";
5+
6+
import styles from "./AIModeSettings.module.css";
7+
import { HelpCallout } from "../../components/HelpCallout";
8+
import { AIMode } from "../../api";
9+
10+
interface Props {
11+
defaultAIMode: AIMode;
12+
updateAIMode: (aiMode: AIMode) => void;
13+
}
14+
15+
export const AIModeSettings = ({ defaultAIMode, updateAIMode }: Props) => {
16+
const [aiMode, setAIMode] = useState<AIMode>(defaultAIMode || AIMode.DataAndOpenAI);
17+
const { t } = useTranslation();
18+
19+
const aiModeId = useId("aiMode");
20+
const aiModeFieldId = useId("aiModeField");
21+
22+
const handleModeChange = (selectedMode: AIMode) => {
23+
setAIMode(selectedMode);
24+
updateAIMode(selectedMode);
25+
};
26+
27+
const getModePosition = (mode: AIMode): string => {
28+
switch (mode) {
29+
case AIMode.DataOnly:
30+
return "0%";
31+
case AIMode.OpenAIOnly:
32+
return "33.33%";
33+
case AIMode.DataAndOpenAI:
34+
return "66.67%";
35+
default:
36+
return "66.67%";
37+
}
38+
};
39+
40+
return (
41+
<Stack className={styles.container} tokens={{ childrenGap: 10 }}>
42+
<HelpCallout labelId={aiModeId} fieldId={aiModeFieldId} helpText={t("helpTexts.aiMode")} label={t("labels.aiMode.label")} />
43+
44+
<div className={styles.optionSlider}>
45+
<div className={styles.sliderTrack}>
46+
<div className={styles.sliderThumb} style={{ left: getModePosition(aiMode) }}>
47+
{aiMode === AIMode.DataOnly && t("labels.aiMode.options.dataOnly")}
48+
{aiMode === AIMode.OpenAIOnly && t("labels.aiMode.options.openaiOnly")}
49+
{aiMode === AIMode.DataAndOpenAI && t("labels.aiMode.options.dataAndOpenai")}
50+
</div>
51+
52+
<div className={styles.sliderOptions}>
53+
<div
54+
className={`${styles.sliderOption} ${aiMode === AIMode.DataOnly ? styles.active : ""}`}
55+
onClick={() => handleModeChange(AIMode.DataOnly)}
56+
>
57+
{t("labels.aiMode.options.dataOnly")}
58+
</div>
59+
<div
60+
className={`${styles.sliderOption} ${aiMode === AIMode.OpenAIOnly ? styles.active : ""}`}
61+
onClick={() => handleModeChange(AIMode.OpenAIOnly)}
62+
>
63+
{t("labels.aiMode.options.openaiOnly")}
64+
</div>
65+
<div
66+
className={`${styles.sliderOption} ${aiMode === AIMode.DataAndOpenAI ? styles.active : ""}`}
67+
onClick={() => handleModeChange(AIMode.DataAndOpenAI)}
68+
>
69+
{t("labels.aiMode.options.dataAndOpenai")}
70+
</div>
71+
</div>
72+
</div>
73+
</div>
74+
</Stack>
75+
);
76+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./AIModeSettings";

0 commit comments

Comments
 (0)