large number of PR comments addressed

This commit is contained in:
Evan Lohn 2025-01-31 21:20:51 -08:00
parent 118e8afbef
commit 5a95a5c9fd
37 changed files with 244 additions and 212 deletions

View File

@ -60,5 +60,5 @@ def process_llm_stream(
writer,
)
logger.info(f"Full answer: {full_answer}")
logger.debug(f"Full answer: {full_answer}")
return cast(AIMessageChunk, tool_call_chunk)

View File

@ -17,11 +17,11 @@ def format_initial_sub_answers(
) -> SubQuestionResultsUpdate:
now_start = datetime.now()
logger.info(f"--------{now_start}--------INGEST ANSWERS---")
logger.debug(f"--------{now_start}--------INGEST ANSWERS---")
documents = []
context_documents = []
cited_documents = []
answer_results = state.answer_results if hasattr(state, "answer_results") else []
answer_results = state.answer_results
for answer_result in answer_results:
documents.extend(answer_result.verified_reranked_documents)
context_documents.extend(answer_result.context_documents)

View File

@ -29,6 +29,7 @@ from onyx.agents.agent_search.shared_graph_utils.utils import write_custom_event
from onyx.chat.models import AgentAnswerPiece
from onyx.chat.models import StreamStopInfo
from onyx.chat.models import StreamStopReason
from onyx.chat.models import StreamType
from onyx.configs.agent_configs import AGENT_MAX_ANSWER_CONTEXT_DOCS
from onyx.utils.logger import setup_logger
@ -45,7 +46,7 @@ def generate_sub_answer(
graph_config = cast(GraphConfig, config["metadata"]["config"])
question = state.question
state.verified_reranked_documents
level, question_nr = parse_question_id(state.question_id)
level, question_num = parse_question_id(state.question_id)
context_docs = state.context_documents[:AGENT_MAX_ANSWER_CONTEXT_DOCS]
persona_contextualized_prompt = get_persona_agent_prompt_expressions(
graph_config.inputs.search_request.persona
@ -58,7 +59,7 @@ def generate_sub_answer(
AgentAnswerPiece(
answer_piece=answer_str,
level=level,
level_question_nr=question_nr,
level_question_num=question_num,
answer_type="agent_sub_answer",
),
writer,
@ -90,7 +91,7 @@ def generate_sub_answer(
AgentAnswerPiece(
answer_piece=content,
level=level,
level_question_nr=question_nr,
level_question_num=question_num,
answer_type="agent_sub_answer",
),
writer,
@ -113,9 +114,9 @@ def generate_sub_answer(
stop_event = StreamStopInfo(
stop_reason=StreamStopReason.FINISHED,
stream_type="sub_answer",
stream_type=StreamType.SUB_ANSWER,
level=level,
level_question_nr=question_nr,
level_question_num=question_num,
)
write_custom_event("stream_finished", stop_event, writer)

View File

@ -35,13 +35,13 @@ def parallelize_initial_sub_question_answering(
"answer_query_subgraph",
AnswerQuestionInput(
question=question,
question_id=make_question_id(0, question_nr + 1),
question_id=make_question_id(0, question_num + 1),
log_messages=[
f"{edge_start_time} -- Main Edge - Parallelize Initial Sub-question Answering"
],
),
)
for question_nr, question in enumerate(state.initial_sub_questions)
for question_num, question in enumerate(state.initial_sub_questions)
]
else:

View File

@ -112,7 +112,7 @@ def generate_initial_answer(
id=tool_response.id,
response=tool_response.response,
level=0,
level_question_nr=0, # 0, 0 is the base question
level_question_num=0, # 0, 0 is the base question
),
writer,
)
@ -123,7 +123,7 @@ def generate_initial_answer(
AgentAnswerPiece(
answer_piece=UNKNOWN_ANSWER,
level=0,
level_question_nr=0,
level_question_num=0,
answer_type="agent_level_answer",
),
writer,
@ -142,7 +142,7 @@ def generate_initial_answer(
good_qa_list: list[str] = []
sub_question_nr = 1
sub_question_num = 1
for decomp_answer_result in decomp_answer_results:
decomp_questions.append(decomp_answer_result.question)
@ -155,10 +155,10 @@ def generate_initial_answer(
SUB_QUESTION_ANSWER_TEMPLATE.format(
sub_question=decomp_answer_result.question,
sub_answer=decomp_answer_result.answer,
sub_question_nr=sub_question_nr,
sub_question_num=sub_question_num,
)
)
sub_question_nr += 1
sub_question_num += 1
# Determine which base prompt to use given the sub-question information
if len(good_qa_list) > 0:
@ -212,7 +212,7 @@ def generate_initial_answer(
AgentAnswerPiece(
answer_piece=content,
level=0,
level_question_nr=0,
level_question_num=0,
answer_type="agent_level_answer",
),
writer,

View File

@ -35,13 +35,13 @@ def parallelize_initial_sub_question_answering(
"answer_sub_question_subgraphs",
AnswerQuestionInput(
question=question,
question_id=make_question_id(0, question_nr + 1),
question_id=make_question_id(0, question_num + 1),
log_messages=[
f"{edge_start_time} -- Main Edge - Parallelize Initial Sub-question Answering"
],
),
)
for question_nr, question in enumerate(state.initial_sub_questions)
for question_num, question in enumerate(state.initial_sub_questions)
]
else:

View File

@ -35,6 +35,7 @@ from onyx.agents.agent_search.shared_graph_utils.utils import (
from onyx.agents.agent_search.shared_graph_utils.utils import write_custom_event
from onyx.chat.models import StreamStopInfo
from onyx.chat.models import StreamStopReason
from onyx.chat.models import StreamType
from onyx.chat.models import SubQuestionPiece
from onyx.configs.agent_configs import AGENT_NUM_DOCS_FOR_DECOMPOSITION
@ -55,7 +56,6 @@ def decompose_orig_question(
history = build_history_prompt(graph_config, question)
# Use the initial search results to inform the decomposition
sample_doc_str = state.sample_doc_str if hasattr(state, "sample_doc_str") else ""
agent_start_time = datetime.now()
# Initial search to inform decomposition. Just get top 3 fits
@ -91,7 +91,7 @@ def decompose_orig_question(
SubQuestionPiece(
sub_question=question,
level=0,
level_question_nr=0,
level_question_num=0,
),
writer,
)
@ -102,7 +102,7 @@ def decompose_orig_question(
stop_event = StreamStopInfo(
stop_reason=StreamStopReason.FINISHED,
stream_type="sub_questions",
stream_type=StreamType.SUB_QUESTIONS,
level=0,
)
write_custom_event("stream_finished", stop_event, writer)

View File

@ -22,7 +22,7 @@ def format_initial_sub_answers(
documents = []
context_documents = []
cited_documents = []
answer_results = state.answer_results if hasattr(state, "answer_results") else []
answer_results = state.answer_results
for answer_result in answer_results:
documents.extend(answer_result.verified_reranked_documents)
context_documents.extend(answer_result.context_documents)

View File

@ -60,13 +60,13 @@ def parallelize_initial_sub_question_answering(
"answer_query_subgraph",
AnswerQuestionInput(
question=question,
question_id=make_question_id(0, question_nr + 1),
question_id=make_question_id(0, question_num + 1),
log_messages=[
f"{edge_start_time} -- Main Edge - Parallelize Initial Sub-question Answering"
],
),
)
for question_nr, question in enumerate(state.initial_sub_questions)
for question_num, question in enumerate(state.initial_sub_questions)
]
else:
@ -100,13 +100,13 @@ def parallelize_refined_sub_question_answering(
"answer_refined_question_subgraphs",
AnswerQuestionInput(
question=question_data.sub_question,
question_id=make_question_id(1, question_nr),
question_id=make_question_id(1, question_num),
log_messages=[
f"{edge_start_time} -- Main Edge - Parallelize Refined Sub-question Answering"
],
),
)
for question_nr, question_data in state.refined_sub_questions.items()
for question_num, question_data in state.refined_sub_questions.items()
]
else:

View File

@ -103,16 +103,16 @@ def create_refined_sub_questions(
raise ValueError("LLM response is not a string")
refined_sub_question_dict = {}
for sub_question_nr, sub_question in enumerate(parsed_response):
for sub_question_num, sub_question in enumerate(parsed_response):
refined_sub_question = FollowUpSubQuestion(
sub_question=sub_question,
sub_question_id=make_question_id(1, sub_question_nr + 1),
sub_question_id=make_question_id(1, sub_question_num + 1),
verified=False,
answered=False,
answer="",
)
refined_sub_question_dict[sub_question_nr + 1] = refined_sub_question
refined_sub_question_dict[sub_question_num + 1] = refined_sub_question
return RefinedQuestionDecompositionUpdate(
refined_sub_questions=refined_sub_question_dict,

View File

@ -26,6 +26,7 @@ from onyx.agents.agent_search.shared_graph_utils.utils import format_docs
from onyx.agents.agent_search.shared_graph_utils.utils import (
get_langgraph_node_log_string,
)
from onyx.configs.constants import NUM_EXPLORATORY_DOCS
def extract_entities_terms(
@ -53,7 +54,7 @@ def extract_entities_terms(
# first four lines duplicates from generate_initial_answer
question = graph_config.inputs.search_request.query
initial_search_docs = state.exploratory_search_results[:15]
initial_search_docs = state.exploratory_search_results[:NUM_EXPLORATORY_DOCS]
# start with the entity/term/extraction
doc_context = format_docs(initial_search_docs)

View File

@ -112,7 +112,7 @@ def generate_refined_answer(
id=tool_response.id,
response=tool_response.response,
level=1,
level_question_nr=0, # 0, 0 is the base question
level_question_num=0, # 0, 0 is the base question
),
writer,
)
@ -132,10 +132,10 @@ def generate_refined_answer(
initial_good_sub_questions: list[str] = []
new_revised_good_sub_questions: list[str] = []
sub_question_nr = 1
sub_question_num = 1
for decomp_answer_result in decomp_answer_results:
question_level, question_nr = parse_question_id(
question_level, question_num = parse_question_id(
decomp_answer_result.question_id
)
@ -155,12 +155,12 @@ def generate_refined_answer(
SUB_QUESTION_ANSWER_TEMPLATE_REVISED.format(
sub_question=decomp_answer_result.question,
sub_answer=decomp_answer_result.answer,
sub_question_nr=sub_question_nr,
sub_question_num=sub_question_num,
sub_question_type=sub_question_type,
)
)
sub_question_nr += 1
sub_question_num += 1
initial_good_sub_questions = list(set(initial_good_sub_questions))
new_revised_good_sub_questions = list(set(new_revised_good_sub_questions))
@ -239,7 +239,7 @@ def generate_refined_answer(
AgentAnswerPiece(
answer_piece=content,
level=1,
level_question_nr=0,
level_question_num=0,
answer_type="agent_level_answer",
),
writer,

View File

@ -20,7 +20,7 @@ def ingest_refined_answers(
node_start_time = datetime.now()
documents = []
answer_results = state.answer_results if hasattr(state, "answer_results") else []
answer_results = state.answer_results
for answer_result in answer_results:
documents.extend(answer_result.verified_reranked_documents)

View File

@ -20,13 +20,13 @@ logger = setup_logger()
def dispatch_subquestion(
level: int, writer: StreamWriter
) -> Callable[[str, int], None]:
def _helper(sub_question_part: str, num: int) -> None:
def _helper(sub_question_part: str, sep_num: int) -> None:
write_custom_event(
"decomp_qs",
SubQuestionPiece(
sub_question=sub_question_part,
level=level,
level_question_nr=num,
level_question_num=sep_num,
),
writer,
)

View File

@ -41,9 +41,9 @@ def expand_queries(
llm = graph_config.tooling.fast_llm
sub_question_id = state.sub_question_id
if sub_question_id is None:
level, question_nr = 0, 0
level, question_num = 0, 0
else:
level, question_nr = parse_question_id(sub_question_id)
level, question_num = parse_question_id(sub_question_id)
msg = [
HumanMessage(
@ -52,7 +52,7 @@ def expand_queries(
]
llm_response_list = dispatch_separated(
llm.stream(prompt=msg), dispatch_subquery(level, question_nr, writer)
llm.stream(prompt=msg), dispatch_subquery(level, question_num, writer)
)
llm_response = merge_message_runs(llm_response_list, chunk_separator="")[0].content

View File

@ -30,7 +30,7 @@ def format_results(
config: RunnableConfig,
writer: StreamWriter = lambda _: None,
) -> ExpandedRetrievalUpdate:
level, question_nr = parse_question_id(state.sub_question_id or "0_0")
level, question_num = parse_question_id(state.sub_question_id or "0_0")
query_info = get_query_info(state.query_retrieval_results)
graph_config = cast(GraphConfig, config["metadata"]["config"])
@ -38,7 +38,7 @@ def format_results(
reranked_documents = state.reranked_documents
if not (level == 0 and question_nr == 0):
if not (level == 0 and question_num == 0):
if len(reranked_documents) == 0:
# The sub-question is used as the last query. If no verified documents are found, stream
# the top 3 for that one. We may want to revisit this.
@ -63,7 +63,7 @@ def format_results(
id=tool_response.id,
response=tool_response.response,
level=level,
level_question_nr=question_nr,
level_question_num=question_num,
),
writer,
)

View File

@ -15,7 +15,7 @@ logger = setup_logger()
def dispatch_subquery(
level: int, question_nr: int, writer: StreamWriter
level: int, question_num: int, writer: StreamWriter
) -> Callable[[str, int], None]:
def helper(token: str, num: int) -> None:
write_custom_event(
@ -23,7 +23,7 @@ def dispatch_subquery(
SubQueryPiece(
sub_query=token,
level=level,
level_question_nr=question_nr,
level_question_num=question_num,
query_id=num,
),
writer,

View File

@ -112,7 +112,7 @@ def llm_tool_choice(
# If no tool calls are emitted by the LLM, we should not choose a tool
if len(tool_message.tool_calls) == 0:
logger.info("No tool calls emitted by LLM")
logger.debug("No tool calls emitted by LLM")
return ToolChoiceUpdate(
tool_choice=None,
)
@ -142,7 +142,7 @@ def llm_tool_choice(
f"Tool call attempted with tool {selected_tool}, request {selected_tool_call_request}"
)
logger.info(f"Selected tool: {selected_tool.name}")
logger.debug(f"Selected tool: {selected_tool.name}")
logger.debug(f"Selected tool call request: {selected_tool_call_request}")
return ToolChoiceUpdate(

View File

@ -29,6 +29,7 @@ from onyx.configs.agent_configs import ALLOW_REFINEMENT
from onyx.configs.agent_configs import INITIAL_SEARCH_DECOMPOSITION_ENABLED
from onyx.context.search.models import SearchRequest
from onyx.db.engine import get_session_context_manager
from onyx.llm.factory import get_default_llms
from onyx.tools.tool_runner import ToolCallKickoff
from onyx.utils.logger import setup_logger
@ -144,8 +145,6 @@ def run_basic_graph(
if __name__ == "__main__":
from onyx.llm.factory import get_default_llms
for _ in range(1):
query_start_time = datetime.now()
logger.debug(f"Start at {query_start_time}")
@ -188,29 +187,29 @@ if __name__ == "__main__":
elif isinstance(output, ExtendedToolResponse):
tool_responses.append(output.response)
logger.info(
f" ---- ET {output.level} - {output.level_question_nr} | "
f" ---- ET {output.level} - {output.level_question_num} | "
)
elif isinstance(output, SubQueryPiece):
logger.info(
f"Sq {output.level} - {output.level_question_nr} - {output.sub_query} | "
f"Sq {output.level} - {output.level_question_num} - {output.sub_query} | "
)
elif isinstance(output, SubQuestionPiece):
logger.info(
f"SQ {output.level} - {output.level_question_nr} - {output.sub_question} | "
f"SQ {output.level} - {output.level_question_num} - {output.sub_question} | "
)
elif (
isinstance(output, AgentAnswerPiece)
and output.answer_type == "agent_sub_answer"
):
logger.info(
f" ---- SA {output.level} - {output.level_question_nr} {output.answer_piece} | "
f" ---- SA {output.level} - {output.level_question_num} {output.answer_piece} | "
)
elif (
isinstance(output, AgentAnswerPiece)
and output.answer_type == "agent_level_answer"
):
logger.info(
f" ---------- FA {output.level} - {output.level_question_nr} {output.answer_piece} | "
f" ---------- FA {output.level} - {output.level_question_num} {output.answer_piece} | "
)
elif isinstance(output, RefinedAnswerImprovement):
logger.info(

View File

@ -10,7 +10,6 @@ from onyx.agents.agent_search.shared_graph_utils.prompts import HISTORY_PROMPT
from onyx.agents.agent_search.shared_graph_utils.utils import (
get_persona_agent_prompt_expressions,
)
from onyx.agents.agent_search.shared_graph_utils.utils import get_today_prompt
from onyx.agents.agent_search.shared_graph_utils.utils import remove_document_citations
from onyx.agents.agent_search.shared_graph_utils.utils import summarize_history
from onyx.configs.agent_configs import AGENT_MAX_STATIC_HISTORY_WORD_LENGTH
@ -19,6 +18,7 @@ from onyx.llm.interfaces import LLMConfig
from onyx.llm.utils import get_max_input_tokens
from onyx.natural_language_processing.utils import get_tokenizer
from onyx.natural_language_processing.utils import tokenizer_trim_content
from onyx.prompts.prompt_utils import build_date_time_string
def build_sub_question_answer_prompt(
@ -32,12 +32,12 @@ def build_sub_question_answer_prompt(
content=persona_specification,
)
date_str = get_today_prompt()
date_str = build_date_time_string()
docs_format_list = [
f"""Document Number: [D{doc_nr + 1}]\n
f"""Document Number: [D{doc_num + 1}]\n
Content: {doc.combined_content}\n\n"""
for doc_nr, doc in enumerate(docs)
for doc_num, doc in enumerate(docs)
]
docs_str = "\n\n".join(docs_format_list)
@ -126,7 +126,7 @@ def get_prompt_enrichment_components(
history = build_history_prompt(config, config.inputs.search_request.query)
date_str = get_today_prompt()
date_str = build_date_time_string()
return AgentPromptEnrichmentComponents(
persona_prompts=persona_prompts,

View File

@ -765,11 +765,11 @@ You are an assistant for question-answering tasks. Here is more information abou
"""
SUB_QUESTION_ANSWER_TEMPLATE = """\n
Sub-Question: Q{sub_question_nr}\n Sub-Question:\n - \n{sub_question}\n --\nAnswer:\n -\n {sub_answer}\n\n
Sub-Question: Q{sub_question_num}\n Sub-Question:\n - \n{sub_question}\n --\nAnswer:\n -\n {sub_answer}\n\n
"""
SUB_QUESTION_ANSWER_TEMPLATE_REVISED = """\n
Sub-Question: Q{sub_question_nr}\n
Sub-Question: Q{sub_question_num}\n
Type:
\n----\n
{sub_question_type}
@ -787,12 +787,12 @@ Sub-Question:
SUB_QUESTION_ANSWER_TEMPLATE_REVISED = """\n
Sub-Question: Q{sub_question_nr}\n Type: {sub_question_type}\n Sub-Question:\n
Sub-Question: Q{sub_question_num}\n Type: {sub_question_type}\n Sub-Question:\n
- \n{sub_question}\n --\nAnswer:\n -\n {sub_answer}\n\n
"""
SUB_QUESTION_SEARCH_RESULTS_TEMPLATE = """\n
Sub-Question: Q{sub_question_nr}\n Sub-Question:\n - \n{sub_question}\n --\nRelevant Documents:\n
Sub-Question: Q{sub_question_num}\n Sub-Question:\n - \n{sub_question}\n --\nRelevant Documents:\n
-\n {formatted_sub_question_docs}\n\n
"""

View File

@ -33,7 +33,6 @@ from onyx.agents.agent_search.shared_graph_utils.prompts import (
from onyx.agents.agent_search.shared_graph_utils.prompts import (
ASSISTANT_SYSTEM_PROMPT_PERSONA,
)
from onyx.agents.agent_search.shared_graph_utils.prompts import DATE_PROMPT
from onyx.agents.agent_search.shared_graph_utils.prompts import (
HISTORY_CONTEXT_SUMMARY_PROMPT,
)
@ -45,11 +44,13 @@ from onyx.chat.models import PromptConfig
from onyx.chat.models import SectionRelevancePiece
from onyx.chat.models import StreamStopInfo
from onyx.chat.models import StreamStopReason
from onyx.chat.models import StreamType
from onyx.chat.prompt_builder.answer_prompt_builder import AnswerPromptBuilder
from onyx.configs.chat_configs import CHAT_TARGET_CHUNK_PERCENTAGE
from onyx.configs.chat_configs import MAX_CHUNKS_FED_TO_CHAT
from onyx.configs.constants import DEFAULT_PERSONA_ID
from onyx.configs.constants import DISPATCH_SEP_CHAR
from onyx.configs.constants import FORMAT_DOCS_SEPARATOR
from onyx.context.search.enums import LLMEvaluationType
from onyx.context.search.models import InferenceSection
from onyx.context.search.models import RetrievalDetails
@ -81,10 +82,10 @@ def normalize_whitespace(text: str) -> str:
def format_docs(docs: Sequence[InferenceSection]) -> str:
formatted_doc_list = []
for doc_nr, doc in enumerate(docs):
formatted_doc_list.append(f"Document D{doc_nr + 1}:\n{doc.combined_content}")
for doc_num, doc in enumerate(docs):
formatted_doc_list.append(f"Document D{doc_num + 1}:\n{doc.combined_content}")
return "\n\n".join(formatted_doc_list)
return FORMAT_DOCS_SEPARATOR.join(formatted_doc_list)
def format_docs_content_flat(docs: Sequence[InferenceSection]) -> str:
@ -93,7 +94,7 @@ def format_docs_content_flat(docs: Sequence[InferenceSection]) -> str:
for _, doc in enumerate(docs):
formatted_doc_list.append(f"\n...{doc.combined_content}\n")
return "\n\n".join(formatted_doc_list)
return FORMAT_DOCS_SEPARATOR.join(formatted_doc_list)
def clean_and_parse_list_string(json_string: str) -> list[dict]:
@ -289,20 +290,27 @@ def get_persona_agent_prompt_expressions(persona: Persona | None) -> PersonaExpr
)
def make_question_id(level: int, question_nr: int) -> str:
return f"{level}_{question_nr}"
def make_question_id(level: int, question_num: int) -> str:
return f"{level}_{question_num}"
def parse_question_id(question_id: str) -> tuple[int, int]:
level, question_nr = question_id.split("_")
return int(level), int(question_nr)
level, question_num = question_id.split("_")
return int(level), int(question_num)
def _dispatch_nonempty(
content: str, dispatch_event: Callable[[str, int], None], num: int
content: str, dispatch_event: Callable[[str, int], None], sep_num: int
) -> None:
"""
Dispatch a content string if it is not empty using the given callback.
This function is used in the context of dispatching some arbitrary number
of similar objects which are separated by a separator during the LLM stream.
The callback expects a sep_num denoting which object is being dispatched; these
numbers go from 1 to however many strings the LLM decides to stream.
"""
if content != "":
dispatch_event(content, num)
dispatch_event(content, sep_num)
def dispatch_separated(
@ -331,16 +339,12 @@ def dispatch_separated(
def dispatch_main_answer_stop_info(level: int, writer: StreamWriter) -> None:
stop_event = StreamStopInfo(
stop_reason=StreamStopReason.FINISHED,
stream_type="main_answer",
stream_type=StreamType.MAIN_ANSWER,
level=level,
)
write_custom_event("stream_finished", stop_event, writer)
def get_today_prompt() -> str:
return DATE_PROMPT.format(date=datetime.now().strftime("%A, %B %d, %Y"))
def retrieve_search_docs(
search_tool: SearchTool, question: str
) -> list[InferenceSection]:
@ -379,13 +383,8 @@ def summarize_history(
)
history_response = model.invoke(history_context_prompt)
if isinstance(history_response.content, str):
history_context_response_str = history_response.content
else:
history_context_response_str = ""
return history_context_response_str
assert isinstance(history_response.content, str)
return history_response.content
# taken from langchain_core.runnables.schema

View File

@ -19,6 +19,7 @@ from onyx.chat.models import CitationInfo
from onyx.chat.models import OnyxAnswerPiece
from onyx.chat.models import StreamStopInfo
from onyx.chat.models import StreamStopReason
from onyx.chat.models import SubQuestionKey
from onyx.chat.prompt_builder.answer_prompt_builder import AnswerPromptBuilder
from onyx.configs.constants import BASIC_KEY
from onyx.context.search.models import SearchRequest
@ -32,6 +33,8 @@ from onyx.utils.logger import setup_logger
logger = setup_logger()
BASIC_SQ_KEY = SubQuestionKey(level=BASIC_KEY[0], question_num=BASIC_KEY[1])
class Answer:
def __init__(
@ -164,6 +167,7 @@ class Answer:
and packet.answer_piece
and packet.answer_type == "agent_level_answer"
):
assert packet.level is not None
answer_by_level[packet.level] += packet.answer_piece
elif isinstance(packet, OnyxAnswerPiece) and packet.answer_piece:
answer_by_level[BASIC_KEY[0]] += packet.answer_piece
@ -178,19 +182,20 @@ class Answer:
return citations
# TODO: replace tuple of ints with SubQuestionId EVERYWHERE
def citations_by_subquestion(self) -> dict[tuple[int, int], list[CitationInfo]]:
def citations_by_subquestion(self) -> dict[SubQuestionKey, list[CitationInfo]]:
citations_by_subquestion: dict[
tuple[int, int], list[CitationInfo]
SubQuestionKey, list[CitationInfo]
] = defaultdict(list)
for packet in self.processed_streamed_output:
if isinstance(packet, CitationInfo):
if packet.level_question_nr is not None and packet.level is not None:
if packet.level_question_num is not None and packet.level is not None:
citations_by_subquestion[
(packet.level, packet.level_question_nr)
SubQuestionKey(
level=packet.level, question_num=packet.level_question_num
)
].append(packet)
elif packet.level is None:
citations_by_subquestion[BASIC_KEY].append(packet)
citations_by_subquestion[BASIC_SQ_KEY].append(packet)
return citations_by_subquestion
def is_cancelled(self) -> bool:

View File

@ -16,6 +16,8 @@ from onyx.context.search.enums import QueryFlow
from onyx.context.search.enums import RecencyBiasSetting
from onyx.context.search.enums import SearchType
from onyx.context.search.models import RetrievalDocs
from onyx.db.models import SearchDoc as DbSearchDoc
from onyx.file_store.models import FileDescriptor
from onyx.llm.override_models import PromptOverride
from onyx.tools.models import ToolCallFinalResult
from onyx.tools.models import ToolCallKickoff
@ -41,16 +43,19 @@ class LlmDoc(BaseModel):
match_highlights: list[str] | None
class SubQuestionIdentifier(BaseModel):
level: int | None = None
level_question_num: int | None = None
# First chunk of info for streaming QA
class QADocsResponse(RetrievalDocs):
class QADocsResponse(RetrievalDocs, SubQuestionIdentifier):
rephrased_query: str | None = None
predicted_flow: QueryFlow | None
predicted_search: SearchType | None
applied_source_filters: list[DocumentSource] | None
applied_time_cutoff: datetime | None
recency_bias_multiplier: float
level: int | None = None
level_question_nr: int | None = None
def model_dump(self, *args: list, **kwargs: dict[str, Any]) -> dict[str, Any]: # type: ignore
initial_dict = super().model_dump(mode="json", *args, **kwargs) # type: ignore
@ -67,13 +72,16 @@ class StreamStopReason(Enum):
FINISHED = "finished"
class StreamStopInfo(BaseModel):
class StreamType(Enum):
SUB_QUESTIONS = "sub_questions"
SUB_ANSWER = "sub_answer"
MAIN_ANSWER = "main_answer"
class StreamStopInfo(SubQuestionIdentifier):
stop_reason: StreamStopReason
stream_type: Literal["", "sub_questions", "sub_answer", "main_answer"] = ""
# used to identify the stream that was stopped for agent search
level: int | None = None
level_question_nr: int | None = None
stream_type: StreamType = StreamType.MAIN_ANSWER
def model_dump(self, *args: list, **kwargs: dict[str, Any]) -> dict[str, Any]: # type: ignore
data = super().model_dump(mode="json", *args, **kwargs) # type: ignore
@ -114,11 +122,9 @@ class OnyxAnswerPiece(BaseModel):
# An intermediate representation of citations, later translated into
# a mapping of the citation [n] number to SearchDoc
class CitationInfo(BaseModel):
class CitationInfo(SubQuestionIdentifier):
citation_num: int
document_id: str
level: int | None = None
level_question_nr: int | None = None
class AllCitations(BaseModel):
@ -310,29 +316,22 @@ class PromptConfig(BaseModel):
model_config = ConfigDict(frozen=True)
class SubQueryPiece(BaseModel):
class SubQueryPiece(SubQuestionIdentifier):
sub_query: str
level: int
level_question_nr: int
query_id: int
class AgentAnswerPiece(BaseModel):
class AgentAnswerPiece(SubQuestionIdentifier):
answer_piece: str
level: int
level_question_nr: int
answer_type: Literal["agent_sub_answer", "agent_level_answer"]
class SubQuestionPiece(BaseModel):
class SubQuestionPiece(SubQuestionIdentifier):
sub_question: str
level: int
level_question_nr: int
class ExtendedToolResponse(ToolResponse):
level: int
level_question_nr: int
class ExtendedToolResponse(ToolResponse, SubQuestionIdentifier):
pass
class RefinedAnswerImprovement(BaseModel):
@ -363,3 +362,29 @@ ResponsePart = (
)
AnswerStream = Iterator[AnswerPacket]
class AnswerPostInfo(BaseModel):
ai_message_files: list[FileDescriptor]
qa_docs_response: QADocsResponse | None = None
reference_db_search_docs: list[DbSearchDoc] | None = None
dropped_indices: list[int] | None = None
tool_result: ToolCallFinalResult | None = None
message_specific_citations: MessageSpecificCitations | None = None
class Config:
arbitrary_types_allowed = True
class SubQuestionKey(BaseModel):
level: int
question_num: int
def __hash__(self) -> int:
return hash((self.level, self.question_num))
def __eq__(self, other: object) -> bool:
return isinstance(other, SubQuestionKey) and (
self.level,
self.question_num,
) == (other.level, other.question_num)

View File

@ -2,7 +2,6 @@ import traceback
from collections import defaultdict
from collections.abc import Callable
from collections.abc import Iterator
from dataclasses import dataclass
from functools import partial
from typing import cast
@ -13,6 +12,7 @@ from onyx.chat.chat_utils import create_chat_chain
from onyx.chat.chat_utils import create_temporary_persona
from onyx.chat.models import AgentSearchPacket
from onyx.chat.models import AllCitations
from onyx.chat.models import AnswerPostInfo
from onyx.chat.models import AnswerStyleConfig
from onyx.chat.models import ChatOnyxBotResponse
from onyx.chat.models import CitationConfig
@ -33,6 +33,7 @@ from onyx.chat.models import RefinedAnswerImprovement
from onyx.chat.models import StreamingError
from onyx.chat.models import StreamStopInfo
from onyx.chat.models import StreamStopReason
from onyx.chat.models import SubQuestionKey
from onyx.chat.prompt_builder.answer_prompt_builder import AnswerPromptBuilder
from onyx.chat.prompt_builder.answer_prompt_builder import default_build_system_message
from onyx.chat.prompt_builder.answer_prompt_builder import default_build_user_message
@ -196,9 +197,9 @@ def _handle_search_tool_response_summary(
for db_search_doc in reference_db_search_docs
]
level, question_nr = None, None
level, question_num = None, None
if isinstance(packet, ExtendedToolResponse):
level, question_nr = packet.level, packet.level_question_nr
level, question_num = packet.level, packet.level_question_num
return (
QADocsResponse(
rephrased_query=response_sumary.rephrased_query,
@ -209,7 +210,7 @@ def _handle_search_tool_response_summary(
applied_time_cutoff=response_sumary.final_filters.time_cutoff,
recency_bias_multiplier=response_sumary.recency_bias_multiplier,
level=level,
level_question_nr=question_nr,
level_question_num=question_num,
),
reference_db_search_docs,
dropped_inds,
@ -310,17 +311,6 @@ ChatPacket = (
ChatPacketStream = Iterator[ChatPacket]
# can't store a DbSearchDoc in a Pydantic BaseModel
@dataclass
class AnswerPostInfo:
ai_message_files: list[FileDescriptor]
qa_docs_response: QADocsResponse | None = None
reference_db_search_docs: list[DbSearchDoc] | None = None
dropped_indices: list[int] | None = None
tool_result: ToolCallFinalResult | None = None
message_specific_citations: MessageSpecificCitations | None = None
def stream_chat_message_objects(
new_msg_req: CreateChatMessageRequest,
user: User | None,
@ -794,18 +784,22 @@ def stream_chat_message_objects(
# tool_result = None
# TODO: different channels for stored info when it's coming from the agent flow
info_by_subq: dict[tuple[int, int], AnswerPostInfo] = defaultdict(
info_by_subq: dict[SubQuestionKey, AnswerPostInfo] = defaultdict(
lambda: AnswerPostInfo(ai_message_files=[])
)
refined_answer_improvement = True
for packet in answer.processed_streamed_output:
if isinstance(packet, ToolResponse):
level, level_question_nr = (
(packet.level, packet.level_question_nr)
level, level_question_num = (
(packet.level, packet.level_question_num)
if isinstance(packet, ExtendedToolResponse)
else BASIC_KEY
)
info = info_by_subq[(level, level_question_nr)]
assert level is not None
assert level_question_num is not None
info = info_by_subq[
SubQuestionKey(level=level, question_num=level_question_num)
]
# TODO: don't need to dedupe here when we do it in agent flow
if packet.id == SEARCH_RESPONSE_SUMMARY_ID:
(
@ -928,13 +922,15 @@ def stream_chat_message_objects(
yield packet
else:
if isinstance(packet, ToolCallFinalResult):
level, level_question_nr = (
(packet.level, packet.level_question_nr)
level, level_question_num = (
(packet.level, packet.level_question_num)
if packet.level is not None
and packet.level_question_nr is not None
and packet.level_question_num is not None
else BASIC_KEY
)
info = info_by_subq[(level, level_question_nr)]
info = info_by_subq[
SubQuestionKey(level=level, question_num=level_question_num)
]
info.tool_result = packet
yield cast(ChatPacket, packet)
logger.debug("Reached end of stream")
@ -971,26 +967,30 @@ def stream_chat_message_objects(
tool_name_to_tool_id[tool.name] = tool_id
subq_citations = answer.citations_by_subquestion()
for pair in subq_citations:
level, level_question_nr = pair
info = info_by_subq[(level, level_question_nr)]
for subq_key in subq_citations:
info = info_by_subq[subq_key]
logger.debug("Post-LLM answer processing")
if info.reference_db_search_docs:
info.message_specific_citations = _translate_citations(
citations_list=subq_citations[pair],
citations_list=subq_citations[subq_key],
db_docs=info.reference_db_search_docs,
)
# TODO: AllCitations should contain subq info?
if not answer.is_cancelled():
yield AllCitations(citations=subq_citations[pair])
yield AllCitations(citations=subq_citations[subq_key])
# Saving Gen AI answer and responding with message info
info = (
info_by_subq[BASIC_KEY]
info_by_subq[SubQuestionKey(level=BASIC_KEY[0], question_num=BASIC_KEY[1])]
if BASIC_KEY in info_by_subq
else info_by_subq[AGENT_SEARCH_INITIAL_KEY]
else info_by_subq[
SubQuestionKey(
level=AGENT_SEARCH_INITIAL_KEY[0],
question_num=AGENT_SEARCH_INITIAL_KEY[1],
)
]
)
gen_ai_response_message = partial_response(
message=answer.llm_answer,
@ -1025,7 +1025,11 @@ def stream_chat_message_objects(
agent_answers = answer.llm_answer_by_level()
while next_level in agent_answers:
next_answer = agent_answers[next_level]
info = info_by_subq[(next_level, AGENT_SEARCH_INITIAL_KEY[1])]
info = info_by_subq[
SubQuestionKey(
level=next_level, question_num=AGENT_SEARCH_INITIAL_KEY[1]
)
]
next_answer_message = create_new_chat_message(
chat_session_id=chat_session_id,
parent_message=prev_message,

View File

@ -43,6 +43,8 @@ BASIC_KEY = (-1, -1)
AGENT_SEARCH_INITIAL_KEY = (0, 0)
CANCEL_CHECK_INTERVAL = 20
DISPATCH_SEP_CHAR = "\n"
FORMAT_DOCS_SEPARATOR = "\n\n"
NUM_EXPLORATORY_DOCS = 15
# Postgres connection constants for application_name
POSTGRES_WEB_APP_NAME = "web"
POSTGRES_INDEXER_APP_NAME = "indexer"

View File

@ -889,7 +889,7 @@ def translate_db_sub_questions_to_server_objects(
sub_questions.append(
SubQuestionDetail(
level=sub_question.level,
level_question_nr=sub_question.level_question_num,
level_question_num=sub_question.level_question_num,
question=sub_question.sub_question,
answer=sub_question.sub_answer,
sub_queries=sub_queries,
@ -1012,7 +1012,7 @@ def log_agent_sub_question_results(
now = datetime.now()
for sub_question_answer_result in sub_question_answer_results:
level, level_question_nr = [
level, level_question_num = [
int(x) for x in sub_question_answer_result.question_id.split("_")
]
sub_question = sub_question_answer_result.question
@ -1025,7 +1025,7 @@ def log_agent_sub_question_results(
chat_session_id=chat_session_id,
primary_question_id=primary_message_id,
level=level,
level_question_num=level_question_nr,
level_question_num=level_question_num,
sub_question=sub_question,
sub_answer=sub_answer,
sub_question_doc_results=sub_document_results,
@ -1033,7 +1033,6 @@ def log_agent_sub_question_results(
db_session.add(sub_question_object)
db_session.commit()
# db_session.flush()
sub_question_id = sub_question_object.id
@ -1047,7 +1046,6 @@ def log_agent_sub_question_results(
db_session.add(sub_query_object)
db_session.commit()
# db_session.flush()
search_docs = chunks_or_sections_to_search_docs(sub_query.search_results)
for doc in search_docs:

View File

@ -429,8 +429,6 @@ def handle_new_chat_message(
),
is_connected=is_connected_func,
):
# with open('chat_packets.log', 'a') as log_file:
# log_file.write(json.dumps(packet) + '\n')
yield json.dumps(packet) if isinstance(packet, dict) else packet
except Exception as e:

View File

@ -215,7 +215,7 @@ class SubQueryDetail(BaseModel):
class SubQuestionDetail(BaseModel):
level: int
level_question_nr: int
level_question_num: int
question: str
answer: str
sub_queries: list[SubQueryDetail] | None = None

View File

@ -43,7 +43,7 @@ class ToolCallFinalResult(ToolCallKickoff):
)
# agentic additions; only need to set during agentic tool calls
level: int | None = None
level_question_nr: int | None = None
level_question_num: int | None = None
class DynamicSchemaInfo(BaseModel):

View File

@ -277,10 +277,10 @@ export function ChatPage({
(assistant) => assistant.id === existingChatSessionAssistantId
)
: defaultAssistantId !== undefined
? availableAssistants.find(
(assistant) => assistant.id === defaultAssistantId
)
: undefined
? availableAssistants.find(
(assistant) => assistant.id === defaultAssistantId
)
: undefined
);
// Gather default temperature settings
const search_param_temperature = searchParams.get(
@ -290,12 +290,12 @@ export function ChatPage({
const defaultTemperature = search_param_temperature
? parseFloat(search_param_temperature)
: selectedAssistant?.tools.some(
(tool) =>
tool.in_code_tool_id === SEARCH_TOOL_ID ||
tool.in_code_tool_id === INTERNET_SEARCH_TOOL_ID
)
? 0
: 0.7;
(tool) =>
tool.in_code_tool_id === SEARCH_TOOL_ID ||
tool.in_code_tool_id === INTERNET_SEARCH_TOOL_ID
)
? 0
: 0.7;
const setSelectedAssistantFromId = (assistantId: number) => {
// NOTE: also intentionally look through available assistants here, so that
@ -1234,8 +1234,8 @@ export function ChatPage({
const currentAssistantId = alternativeAssistantOverride
? alternativeAssistantOverride.id
: alternativeAssistant
? alternativeAssistant.id
: liveAssistant.id;
? alternativeAssistant.id
: liveAssistant.id;
resetInputBar();
let messageUpdates: Message[] | null = null;
@ -1427,7 +1427,7 @@ export function ChatPage({
// Continuously refine the sub_questions based on the packets that we receive
if (
Object.hasOwn(packet, "stop_reason") &&
Object.hasOwn(packet, "level_question_nr")
Object.hasOwn(packet, "level_question_num")
) {
sub_questions = constructSubQuestions(
sub_questions,
@ -1471,8 +1471,8 @@ export function ChatPage({
}
} else if (
Object.hasOwn(packet, "top_documents") &&
Object.hasOwn(packet, "level_question_nr") &&
(packet as DocumentsResponse).level_question_nr != undefined
Object.hasOwn(packet, "level_question_num") &&
(packet as DocumentsResponse).level_question_num != undefined
) {
const documentsResponse = packet as DocumentsResponse;
sub_questions = constructSubQuestions(
@ -1481,12 +1481,12 @@ export function ChatPage({
);
if (
documentsResponse.level_question_nr === 0 &&
documentsResponse.level_question_num === 0 &&
documentsResponse.level == 0
) {
documents = (packet as DocumentsResponse).top_documents;
} else if (
documentsResponse.level_question_nr === 0 &&
documentsResponse.level_question_num === 0 &&
documentsResponse.level == 1
) {
agenticDocs = (packet as DocumentsResponse).top_documents;

View File

@ -158,7 +158,7 @@ export interface DocumentsResponse {
top_documents: OnyxDocument[];
rephrased_query: string | null;
level?: number | null;
level_question_nr?: number | null;
level_question_num?: number | null;
}
export interface FileChatDisplay {
@ -209,7 +209,7 @@ export interface PromptData {
export interface BaseQuestionIdentifier {
level: number;
level_question_nr: number;
level_question_num: number;
}
export interface SubQuestionDetail extends BaseQuestionIdentifier {
@ -239,34 +239,34 @@ export const constructSubQuestions = (
if (!newDetail) {
return subQuestions;
}
if (newDetail.level_question_nr == 0) {
if (newDetail.level_question_num == 0) {
return subQuestions;
}
const updatedSubQuestions = [...subQuestions];
// .filter(
// (sq) => sq.level_question_nr !== 0
// (sq) => sq.level_question_num !== 0
// );
if ("stop_reason" in newDetail) {
console.log("STOP REASON");
console.log(newDetail);
const { level, level_question_nr } = newDetail;
const { level, level_question_num } = newDetail;
let subQuestion = updatedSubQuestions.find(
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
(sq) => sq.level === level && sq.level_question_num === level_question_num
);
if (subQuestion) {
subQuestion.is_complete = true;
}
} else if ("top_documents" in newDetail) {
const { level, level_question_nr, top_documents } = newDetail;
const { level, level_question_num, top_documents } = newDetail;
let subQuestion = updatedSubQuestions.find(
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
(sq) => sq.level === level && sq.level_question_num === level_question_num
);
if (!subQuestion) {
subQuestion = {
level: level ?? 0,
level_question_nr: level_question_nr ?? 0,
level_question_num: level_question_num ?? 0,
question: "",
answer: "",
sub_queries: [],
@ -277,16 +277,16 @@ export const constructSubQuestions = (
}
} else if ("answer_piece" in newDetail) {
// Handle AgentAnswerPiece
const { level, level_question_nr, answer_piece } = newDetail;
const { level, level_question_num, answer_piece } = newDetail;
// Find or create the relevant SubQuestionDetail
let subQuestion = updatedSubQuestions.find(
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
(sq) => sq.level === level && sq.level_question_num === level_question_num
);
if (!subQuestion) {
subQuestion = {
level,
level_question_nr,
level_question_num,
question: "",
answer: "",
sub_queries: [],
@ -299,17 +299,17 @@ export const constructSubQuestions = (
subQuestion.answer += answer_piece;
} else if ("sub_question" in newDetail) {
// Handle SubQuestionPiece
const { level, level_question_nr, sub_question } = newDetail;
const { level, level_question_num, sub_question } = newDetail;
// Find or create the relevant SubQuestionDetail
let subQuestion = updatedSubQuestions.find(
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
(sq) => sq.level === level && sq.level_question_num === level_question_num
);
if (!subQuestion) {
subQuestion = {
level,
level_question_nr,
level_question_num,
question: "",
answer: "",
sub_queries: [],
@ -322,18 +322,18 @@ export const constructSubQuestions = (
subQuestion.question += sub_question;
} else if ("sub_query" in newDetail) {
// Handle SubQueryPiece
const { level, level_question_nr, query_id, sub_query } = newDetail;
const { level, level_question_num, query_id, sub_query } = newDetail;
// Find the relevant SubQuestionDetail
let subQuestion = updatedSubQuestions.find(
(sq) => sq.level === level && sq.level_question_nr === level_question_nr
(sq) => sq.level === level && sq.level_question_num === level_question_num
);
if (!subQuestion) {
// If we receive a sub_query before its parent question, create a placeholder
subQuestion = {
level,
level_question_nr: level_question_nr,
level_question_num: level_question_num,
question: "",
answer: "",
sub_queries: [],

View File

@ -227,7 +227,7 @@ export const AgenticMessage = ({
(question: SubQuestionDetail) => {
setCurrentlyOpenQuestion({
level: question.level,
level_question_nr: question.level_question_nr,
level_question_num: question.level_question_num,
});
setTimeout(() => {
console.log("closing question");

View File

@ -93,7 +93,7 @@ export const useStreamingMessages = (
if (!dynamicSubQuestionsRef.current[i]) {
dynamicSubQuestionsRef.current[i] = {
level: sq.level,
level_question_nr: sq.level_question_nr,
level_question_num: sq.level_question_num,
question: "",
answer: "",
sub_queries: [],
@ -270,11 +270,11 @@ export const useStreamingMessages = (
// Check if this is the last subquestion at level 0
if (
sq.level === 0 &&
sq.level_question_nr ===
sq.level_question_num ===
Math.max(
...subQuestions
.filter((q) => q.level === 0)
.map((q) => q.level_question_nr)
.map((q) => q.level_question_num)
)
) {
console.log("ALLOW STREAMING");

View File

@ -30,7 +30,7 @@ const SubQuestionProgress: React.FC<SubQuestionProgressProps> = ({
{subQuestions.map((sq, index) => (
<TableRow key={index}>
<TableCell>
Level {sq.level}, Q{sq.level_question_nr}
Level {sq.level}, Q{sq.level_question_num}
</TableCell>
<TableCell>
<Popover>

View File

@ -634,16 +634,16 @@ const SubQuestionsDisplay: React.FC<SubQuestionsDisplayProps> = ({
<SubQuestionDisplay
currentlyOpen={
currentlyOpenQuestion?.level === subQuestion.level &&
currentlyOpenQuestion?.level_question_nr ===
subQuestion.level_question_nr
currentlyOpenQuestion?.level_question_num ===
subQuestion.level_question_num
}
currentlyClosed={
currentlyOpenQuestion != null &&
currentlyOpenQuestion != undefined &&
!(
currentlyOpenQuestion.level === subQuestion.level &&
currentlyOpenQuestion.level_question_nr ===
subQuestion.level_question_nr
currentlyOpenQuestion.level_question_num ===
subQuestion.level_question_num
)
}
key={index}
@ -681,16 +681,16 @@ const SubQuestionsDisplay: React.FC<SubQuestionsDisplayProps> = ({
}
currentlyOpen={
currentlyOpenQuestion?.level === subQuestion.level &&
currentlyOpenQuestion?.level_question_nr ===
subQuestion.level_question_nr
currentlyOpenQuestion?.level_question_num ===
subQuestion.level_question_num
}
currentlyClosed={
currentlyOpenQuestion != null &&
currentlyOpenQuestion != undefined &&
!(
currentlyOpenQuestion.level === subQuestion.level &&
currentlyOpenQuestion.level_question_nr ===
subQuestion.level_question_nr
currentlyOpenQuestion.level_question_num ===
subQuestion.level_question_num
)
}
key={index}

View File

@ -21,7 +21,7 @@ export interface ProSearchPacket {
sub_query?: string;
tool_response?: ToolResponse;
level: number;
level_question_nr: number;
level_question_num: number;
}
export interface RefinedAnswerImprovement {
@ -31,26 +31,26 @@ export interface RefinedAnswerImprovement {
export interface AgentAnswerPiece {
answer_piece: string;
level: number;
level_question_nr: number;
level_question_num: number;
answer_type: "agent_sub_answer" | "agent_level_answer";
}
export interface SubQuestionPiece {
sub_question: string;
level: number;
level_question_nr: number;
level_question_num: number;
}
export interface SubQueryPiece {
sub_query: string;
level: number;
level_question_nr: number;
level_question_num: number;
query_id: number;
}
export interface SubQuestionSearchDoc {
context_docs: OnyxDocument[];
level_question_nr: number;
level_question_num: number;
level: number;
}
@ -60,7 +60,7 @@ export interface ToolResponse {
}
export interface ExtendedToolResponse extends ToolResponse {
level: number;
level_question_nr: number;
level_question_num: number;
}
export interface AnswerPiecePacket {
@ -75,7 +75,7 @@ export enum StreamStopReason {
export interface StreamStopInfo {
stop_reason: StreamStopReason;
level?: number;
level_question_nr?: number;
level_question_num?: number;
}
export interface ErrorMessagePacket {