Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Agent search feature #3749

Merged
merged 163 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
163 commits
Select commit Hold shift + click to select a range
4fd6e36
second clean commit
evan-danswer Jan 20, 2025
e4c93be
minor fixes to branch
pablonyx Jan 20, 2025
b9bd2ea
history added to agent flow
joachim-danswer Jan 21, 2025
b7f9e43
pydantic for LangGraph + changed ERT extraction flow
joachim-danswer Jan 22, 2025
8aa82be
prompts that even further motivates to cite docs over sub-q's
joachim-danswer Jan 23, 2025
dd26014
basic search restructure: WIP on fixing tests
evan-danswer Jan 23, 2025
50bacc0
missed files from prev commit
evan-danswer Jan 23, 2025
470c4d1
reworked history messages in agent config
evan-danswer Jan 23, 2025
00cee71
streaming + saving of search docs of no verified ones available
joachim-danswer Jan 20, 2025
4e47c81
fix alembic history
evan-danswer Jan 23, 2025
deea9c8
add agent search frontend
pablonyx Jan 23, 2025
3ced9bc
fix for early cancellation test; solves issue with tasks being destro…
evan-danswer Jan 23, 2025
ca1f176
fixed basic flow citations and second test
evan-danswer Jan 23, 2025
2032fb1
removed print statements, fixed pass through handling
evan-danswer Jan 23, 2025
80d248e
Copy changes
yuhongsun96 Jan 23, 2025
71eafe0
various fixes from Yuhong's list
joachim-danswer Jan 23, 2025
3b298e1
change of sub-question answer if no docs recovered
joachim-danswer Jan 23, 2025
3c3900f
turning off initial search pre route decision
joachim-danswer Jan 23, 2025
1ced892
loser verification prompt
joachim-danswer Jan 23, 2025
e5fa411
EL comments addressed
joachim-danswer Jan 23, 2025
eb6dbf4
build fix
pablonyx Jan 24, 2025
dd494d2
k
pablonyx Jan 23, 2025
d32d1c6
update- reorg
pablonyx Jan 23, 2025
28ba01b
updated + functional
pablonyx Jan 23, 2025
4b0a4a2
k
pablonyx Jan 24, 2025
982040c
WIP, but working basic search using initial tool choice node
evan-danswer Jan 24, 2025
ddbfc65
implemented top-level tool calling + force search
evan-danswer Jan 24, 2025
db20045
fixed chat tests
evan-danswer Jan 24, 2025
2bbe20e
address JR comments
evan-danswer Jan 24, 2025
5e9b2e4
persisting refined answer improvement
evan-danswer Jan 24, 2025
eea6f27
make field nullable
evan-danswer Jan 24, 2025
2d8486b
stop infos when done streaming answers
evan-danswer Jan 24, 2025
5bf6a47
skip reranking for <=1 doc
evan-danswer Jan 24, 2025
46402a9
tmp: force agent search
joachim-danswer Jan 24, 2025
fc60fd0
earlier entity extraction & sharper generation prompts
joachim-danswer Jan 24, 2025
aa8cb44
taking out Extraction for now
joachim-danswer Jan 24, 2025
3b3b0c8
Addressing EL's comments
joachim-danswer Jan 25, 2025
3bf6b77
Replaced additional limit with variable
joachim-danswer Jan 25, 2025
f96a3ee
k
pablonyx Jan 25, 2025
d4e0d0d
quick nit
pablonyx Jan 25, 2025
bf06710
k
pablonyx Jan 25, 2025
1393749
minor update - doc ordering
pablonyx Jan 25, 2025
7d494cd
allowed empty Search Tool for non-agentic search
evan-danswer Jan 26, 2025
f14b282
quick nit
pablonyx Jan 26, 2025
d52a0b9
various improvements
pablonyx Jan 26, 2025
25f6543
update bool
pablonyx Jan 26, 2025
a0af8ee
fix toggling edge case
pablonyx Jan 26, 2025
a224736
update switching logic
pablonyx Jan 26, 2025
50ef511
k
pablonyx Jan 26, 2025
4baf3dc
minor update
pablonyx Jan 26, 2025
8c9577a
refined search + question answering as sub-graphs
joachim-danswer Jan 26, 2025
de5ce8a
sub-graphs for initial question/search
joachim-danswer Jan 26, 2025
7487b15
refined search + question answering as sub-graphs
joachim-danswer Jan 26, 2025
9e9bd44
updated answer_comparison prompt + small cleanup
joachim-danswer Jan 26, 2025
812172f
addressing nits of EL
joachim-danswer Jan 27, 2025
1fe966d
increased timeout to get rid of asyncio logger errors
evan-danswer Jan 27, 2025
3a07093
improved timing
pablonyx Jan 28, 2025
f842e15
nit
pablonyx Jan 28, 2025
568f927
improve regeneration state
pablonyx Jan 28, 2025
0496ec3
remove debug
evan-danswer Jan 28, 2025
f1d9634
always send search response
evan-danswer Jan 28, 2025
9bad129
removed unused files
evan-danswer Jan 28, 2025
efa32a8
use reranking settings and persona during preprocessing in reranker
evan-danswer Jan 28, 2025
1a22af4
AgentPromptConfig in Answer class
evan-danswer Jan 29, 2025
110c9f7
nit
evan-danswer Jan 29, 2025
f2aeeb7
Optimizations: docs for context & history
joachim-danswer Jan 27, 2025
18d9255
application of content limitation ion refined answer as well
joachim-danswer Jan 28, 2025
325892a
cleanup of refined answer generation
joachim-danswer Jan 28, 2025
12d1186
increased logging
joachim-danswer Jan 28, 2025
69e8c5f
agent default changes/restructuring
joachim-danswer Jan 28, 2025
da4a086
added total time to logging
joachim-danswer Jan 28, 2025
4817fa0
average dispatch time collection for sub-answers
joachim-danswer Jan 29, 2025
6bef5ca
persona_prompt improvements
joachim-danswer Jan 29, 2025
91b929d
graph directory renamings
joachim-danswer Jan 29, 2025
ff4df6f
fix for merge error (#3814)
joachim-danswer Jan 29, 2025
4e17fc0
variable renaming
joachim-danswer Jan 29, 2025
7e98936
Enrichment prompts, prompt improvements, dispatch logging & reinsert …
joachim-danswer Jan 29, 2025
73769c6
k
joachim-danswer Jan 29, 2025
d70bbcc
k
joachim-danswer Jan 29, 2025
8fea571
k
joachim-danswer Jan 29, 2025
3d99ad7
var initialization
joachim-danswer Jan 29, 2025
6c7f8ea
first pass at dead code deletion
evan-danswer Jan 29, 2025
48e42af
fix rebase issue
evan-danswer Jan 29, 2025
c062097
post rebase fix
pablonyx Jan 30, 2025
a63b341
latex update
pablonyx Jan 30, 2025
7879ba6
collapsed db migrations post-rebase
evan-danswer Jan 30, 2025
5bff8bc
collapsed db migrations post-rebase (added missing file)
evan-danswer Jan 30, 2025
a43a662
fix revision to match internal alembic state
evan-danswer Jan 30, 2025
1dbf561
fix revision to match internal alembic state
evan-danswer Jan 30, 2025
bd3b194
WIP PR comments
evan-danswer Jan 30, 2025
01e6e9a
fixed errors on import
evan-danswer Jan 30, 2025
c43c232
Tiny nits
yuhongsun96 Jan 30, 2025
23bf50b
address doc
pablonyx Jan 31, 2025
e3e855c
potential question fix
pablonyx Jan 31, 2025
3ca4d53
renamed directories, prompts, and small citation fix
joachim-danswer Jan 30, 2025
f050d28
refininement->refinement
evan-danswer Jan 30, 2025
42780d5
rename of individual_sub_answer_generation
joachim-danswer Jan 30, 2025
55ed6e2
rename initial_answer_generation
joachim-danswer Jan 30, 2025
60fb06d
rename initial_answer_generation pt 2
joachim-danswer Jan 30, 2025
8cbdc6d
fix for refinement renaming
joachim-danswer Jan 30, 2025
0578c31
rename retrieval & consolidate_sub_answers (initial and refinement)
joachim-danswer Jan 30, 2025
f33a2ff
node renaming
evan-danswer Jan 30, 2025
e191e51
fixed find and replace issue
evan-danswer Jan 30, 2025
6442c56
remaining small find replace fix
evan-danswer Jan 30, 2025
0a6808c
rename initial_sub_question_creation
joachim-danswer Jan 30, 2025
ef6e6f9
more renaming
joachim-danswer Jan 30, 2025
3b13380
k
joachim-danswer Jan 30, 2025
ceaaa05
renamings and consolidation of formatting nodes in orig question retr…
joachim-danswer Jan 30, 2025
2517aa3
more renamings
joachim-danswer Jan 30, 2025
b0c3098
more renaming and consolidation
joachim-danswer Jan 30, 2025
2b8cd63
main nodes renaming
evan-danswer Jan 30, 2025
bb6d557
addressing PR comments
evan-danswer Jan 30, 2025
4b82440
finished rebase and fixed issues
evan-danswer Jan 30, 2025
8af4f1d
more renaming
joachim-danswer Jan 31, 2025
756a1cb
answer_refined_question_subgraphs
joachim-danswer Jan 31, 2025
4a0b2a6
additional naming fixes
joachim-danswer Jan 31, 2025
a340529
sync streaming impl
evan-danswer Jan 31, 2025
385b344
addressed TODOs
evan-danswer Jan 31, 2025
23ae454
default values of number of strings and other things
joachim-danswer Jan 31, 2025
1a2760e
improved logging through agent_state plus some default fixes
joachim-danswer Jan 31, 2025
d53dd1e
cited_docs -> cited_documents
joachim-danswer Jan 31, 2025
732861a
rename of documents to verified_reranked_documents
joachim-danswer Jan 31, 2025
0ccd83e
deep_search_a and agent_a_config renaming
joachim-danswer Jan 31, 2025
95fcc00
history summary update
joachim-danswer Jan 31, 2025
d5661ba
history summary fix
joachim-danswer Jan 31, 2025
8342168
initial variable renaming
joachim-danswer Jan 31, 2025
118e8af
reworked config to have logical structure
evan-danswer Jan 31, 2025
5a95a5c
large number of PR comments addressed
evan-danswer Feb 1, 2025
29440f5
alembic heads, basic citations, search pipeline state
evan-danswer Feb 1, 2025
b90e083
major renaming
joachim-danswer Feb 1, 2025
9438f9d
removal of sone unused states/models
joachim-danswer Feb 1, 2025
feaa3b6
fix misc issues
pablonyx Feb 2, 2025
eaffdee
broadly fixed minus some issues
pablonyx Feb 2, 2025
a96728f
prompt piece optimizations
evan-danswer Feb 2, 2025
2adeaae
loading object into model instead of json
evan-danswer Feb 2, 2025
71304e4
always persist in agent search
evan-danswer Feb 2, 2025
e23dd0a
renames + fix of refined answer generation prompt
joachim-danswer Feb 2, 2025
9b6e51b
k
pablonyx Feb 2, 2025
a067b32
Partial Prompt Updates (#3880)
yuhongsun96 Feb 2, 2025
506a9f1
Yuhong
yuhongsun96 Feb 3, 2025
570fe43
log level changes
yuhongsun96 Feb 3, 2025
16265d2
k
yuhongsun96 Feb 3, 2025
9a3ce50
beta
yuhongsun96 Feb 3, 2025
c984c6c
add pro search disable
pablonyx Feb 3, 2025
59c65a4
prompts
yuhongsun96 Feb 3, 2025
87a53d6
quick update
pablonyx Feb 3, 2025
b1e9e03
nit
pablonyx Feb 3, 2025
8f7db92
k
yuhongsun96 Feb 3, 2025
024207e
update
pablonyx Feb 3, 2025
3f6de79
prompt improvements for wekaer models
evan-danswer Feb 3, 2025
3f3b04a
update width
pablonyx Feb 3, 2025
25a57e2
add title and meta-data to doc
joachim-danswer Feb 3, 2025
eb227c0
nit update
joachim-danswer Feb 3, 2025
cb85be4
add proper citation handling
pablonyx Feb 3, 2025
4834ee6
new citation format
joachim-danswer Feb 3, 2025
3cd057d
LangGraph comments
joachim-danswer Feb 3, 2025
7ac6d3e
logging level changes
joachim-danswer Feb 3, 2025
3ce8923
fix for citation update
joachim-danswer Feb 3, 2025
b46c09a
EL comments
joachim-danswer Feb 3, 2025
4b0d22f
prompts
yuhongsun96 Feb 4, 2025
b500c91
cleanup
yuhongsun96 Feb 4, 2025
b928201
fixed rebase issue and some cleanup
evan-danswer Feb 4, 2025
b978191
push various minor updates
pablonyx Feb 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
.vscode/
*.sw?
/backend/tests/regression/answer_quality/search_test_config.yaml
/web/test-results/
/web/test-results/
backend/onyx/agent_search/main/test_data.json
backend/tests/regression/answer_quality/test_data.json
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider adding a .gitignore entry for all test_data.json files recursively using **/test_data.json to catch any future test data files

Suggested change
backend/tests/regression/answer_quality/test_data.json
**/test_data.json

6 changes: 6 additions & 0 deletions .vscode/env_template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,9 @@ BING_API_KEY=<REPLACE THIS>
# Enable the full set of Danswer Enterprise Edition features
# NOTE: DO NOT ENABLE THIS UNLESS YOU HAVE A PAID ENTERPRISE LICENSE (or if you are using this for local testing/development)
ENABLE_PAID_ENTERPRISE_EDITION_FEATURES=False

# Agent Search configs # TODO: Remove give proper namings
AGENT_RETRIEVAL_STATS=False # Note: This setting will incur substantial re-ranking effort
AGENT_RERANKING_STATS=True
AGENT_MAX_QUERY_RETRIEVAL_RESULTS=20
AGENT_RERANKING_MAX_QUERY_RETRIEVAL_RESULTS=20
107 changes: 107 additions & 0 deletions backend/alembic/versions/98a5008d8711_agent_tracking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""agent_tracking

Revision ID: 98a5008d8711
Revises: 2f80c6a2550f
Create Date: 2025-01-29 17:00:00.000001

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from sqlalchemy.dialects.postgresql import UUID

# revision identifiers, used by Alembic.
revision = "98a5008d8711"
down_revision = "2f80c6a2550f"
branch_labels = None
depends_on = None


def upgrade() -> None:
op.create_table(
"agent__search_metrics",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=True),
sa.Column("persona_id", sa.Integer(), nullable=True),
sa.Column("agent_type", sa.String(), nullable=False),
sa.Column("start_time", sa.DateTime(timezone=True), nullable=False),
sa.Column("base_duration_s", sa.Float(), nullable=False),
sa.Column("full_duration_s", sa.Float(), nullable=False),
sa.Column("base_metrics", postgresql.JSONB(), nullable=True),
sa.Column("refined_metrics", postgresql.JSONB(), nullable=True),
sa.Column("all_metrics", postgresql.JSONB(), nullable=True),
sa.ForeignKeyConstraint(
["persona_id"],
["persona.id"],
),
sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
)

# Create sub_question table
op.create_table(
"agent__sub_question",
sa.Column("id", sa.Integer, primary_key=True),
sa.Column("primary_question_id", sa.Integer, sa.ForeignKey("chat_message.id")),
sa.Column(
"chat_session_id", UUID(as_uuid=True), sa.ForeignKey("chat_session.id")
),
sa.Column("sub_question", sa.Text),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: sub_question.sub_question column should be marked as nullable=False since it appears to be a required field

sa.Column(
"time_created", sa.DateTime(timezone=True), server_default=sa.func.now()
),
sa.Column("sub_answer", sa.Text),
sa.Column("sub_question_doc_results", postgresql.JSONB(), nullable=True),
sa.Column("level", sa.Integer(), nullable=False),
sa.Column("level_question_num", sa.Integer(), nullable=False),
)
Comment on lines +45 to +57
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: sub_question table is missing foreign key constraints for primary_question_id and chat_session_id. This could lead to orphaned records if parent records are deleted.

Suggested change
sa.Column("primary_question_id", sa.Integer, sa.ForeignKey("chat_message.id")),
sa.Column(
"chat_session_id", UUID(as_uuid=True), sa.ForeignKey("chat_session.id")
),
sa.Column("sub_question", sa.Text),
sa.Column(
"time_created", sa.DateTime(timezone=True), server_default=sa.func.now()
),
sa.Column("sub_answer", sa.Text),
sa.Column("sub_question_doc_results", postgresql.JSONB(), nullable=True),
sa.Column("level", sa.Integer(), nullable=False),
sa.Column("level_question_num", sa.Integer(), nullable=False),
)
sa.Column("primary_question_id", sa.Integer, sa.ForeignKey("chat_message.id", ondelete="CASCADE")),
sa.Column(
"chat_session_id", UUID(as_uuid=True), sa.ForeignKey("chat_session.id", ondelete="CASCADE")
),
sa.Column("sub_question", sa.Text),
sa.Column(
"time_created", sa.DateTime(timezone=True), server_default=sa.func.now()
),
sa.Column("sub_answer", sa.Text),
sa.Column("sub_question_doc_results", postgresql.JSONB(), nullable=True),
sa.Column("level", sa.Integer(), nullable=False),
sa.Column("level_question_num", sa.Integer(), nullable=False),
)


# Create sub_query table
op.create_table(
"agent__sub_query",
sa.Column("id", sa.Integer, primary_key=True),
sa.Column(
"parent_question_id", sa.Integer, sa.ForeignKey("agent__sub_question.id")
),
sa.Column(
"chat_session_id", UUID(as_uuid=True), sa.ForeignKey("chat_session.id")
),
Comment on lines +64 to +68
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: sub_query table is missing ondelete behavior for foreign key constraints. Should specify CASCADE or SET NULL to handle parent record deletion.

Suggested change
"parent_question_id", sa.Integer, sa.ForeignKey("agent__sub_question.id")
),
sa.Column(
"chat_session_id", UUID(as_uuid=True), sa.ForeignKey("chat_session.id")
),
"parent_question_id", sa.Integer, sa.ForeignKey("agent__sub_question.id", ondelete="CASCADE")
),
sa.Column(
"chat_session_id", UUID(as_uuid=True), sa.ForeignKey("chat_session.id", ondelete="CASCADE")
),

sa.Column("sub_query", sa.Text),
sa.Column(
"time_created", sa.DateTime(timezone=True), server_default=sa.func.now()
),
)

# Create sub_query__search_doc association table
op.create_table(
"agent__sub_query__search_doc",
sa.Column(
"sub_query_id",
sa.Integer,
sa.ForeignKey("agent__sub_query.id"),
primary_key=True,
),
sa.Column(
"search_doc_id",
sa.Integer,
sa.ForeignKey("search_doc.id"),
primary_key=True,
),
)

op.add_column(
"chat_message",
sa.Column(
"refined_answer_improvement",
sa.Boolean(),
nullable=True,
),
)


def downgrade() -> None:
op.drop_column("chat_message", "refined_answer_improvement")
op.drop_table("agent__sub_query__search_doc")
op.drop_table("agent__sub_query")
op.drop_table("agent__sub_question")
op.drop_table("agent__search_metrics")
2 changes: 2 additions & 0 deletions backend/ee/onyx/server/query_and_chat/chat_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def handle_simplified_chat_message(
chunks_below=0,
full_doc=chat_message_req.full_doc,
structured_response_format=chat_message_req.structured_response_format,
use_agentic_search=chat_message_req.use_agentic_search,
)

packets = stream_chat_message_objects(
Expand Down Expand Up @@ -301,6 +302,7 @@ def handle_send_message_simple_with_history(
chunks_below=0,
full_doc=req.full_doc,
structured_response_format=req.structured_response_format,
use_agentic_search=req.use_agentic_search,
)

packets = stream_chat_message_objects(
Expand Down
10 changes: 9 additions & 1 deletion backend/ee/onyx/server/query_and_chat/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class BasicCreateChatMessageRequest(ChunkContext):
# https://platform.openai.com/docs/guides/structured-outputs/introduction
structured_response_format: dict | None = None

# If True, uses agentic search instead of basic search
use_agentic_search: bool = False


class BasicCreateChatMessageWithHistoryRequest(ChunkContext):
# Last element is the new query. All previous elements are historical context
Expand All @@ -71,6 +74,8 @@ class BasicCreateChatMessageWithHistoryRequest(ChunkContext):
# only works if using an OpenAI model. See the following for more details:
# https://platform.openai.com/docs/guides/structured-outputs/introduction
structured_response_format: dict | None = None
# If True, uses agentic search instead of basic search
use_agentic_search: bool = False


class SimpleDoc(BaseModel):
Expand Down Expand Up @@ -120,9 +125,12 @@ class OneShotQARequest(ChunkContext):
# will also disable Thread-based Rewording if specified
query_override: str | None = None

# If True, skips generative an AI response to the search query
# If True, skips generating an AI response to the search query
skip_gen_ai_answer_generation: bool = False

# If True, uses agentic search instead of basic search
use_agentic_search: bool = False

@model_validator(mode="after")
def check_persona_fields(self) -> "OneShotQARequest":
if self.persona_override_config is None and self.persona_id is None:
Expand Down
2 changes: 2 additions & 0 deletions backend/ee/onyx/server/query_and_chat/query_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ def get_answer_stream(
retrieval_details=query_request.retrieval_options,
rerank_settings=query_request.rerank_settings,
db_session=db_session,
use_agentic_search=query_request.use_agentic_search,
skip_gen_ai_answer_generation=query_request.skip_gen_ai_answer_generation,
)

packets = stream_chat_message_objects(
Expand Down
97 changes: 97 additions & 0 deletions backend/onyx/agents/agent_search/basic/graph_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from langgraph.graph import END
from langgraph.graph import START
from langgraph.graph import StateGraph

from onyx.agents.agent_search.basic.states import BasicInput
from onyx.agents.agent_search.basic.states import BasicOutput
from onyx.agents.agent_search.basic.states import BasicState
from onyx.agents.agent_search.orchestration.nodes.basic_use_tool_response import (
basic_use_tool_response,
)
from onyx.agents.agent_search.orchestration.nodes.llm_tool_choice import llm_tool_choice
from onyx.agents.agent_search.orchestration.nodes.prepare_tool_input import (
prepare_tool_input,
)
from onyx.agents.agent_search.orchestration.nodes.tool_call import tool_call
from onyx.utils.logger import setup_logger

logger = setup_logger()


def basic_graph_builder() -> StateGraph:
graph = StateGraph(
state_schema=BasicState,
input=BasicInput,
output=BasicOutput,
)

### Add nodes ###

graph.add_node(
node="prepare_tool_input",
action=prepare_tool_input,
)

graph.add_node(
node="llm_tool_choice",
action=llm_tool_choice,
)

graph.add_node(
node="tool_call",
action=tool_call,
)

graph.add_node(
node="basic_use_tool_response",
action=basic_use_tool_response,
)

### Add edges ###

graph.add_edge(start_key=START, end_key="prepare_tool_input")

graph.add_edge(start_key="prepare_tool_input", end_key="llm_tool_choice")

graph.add_conditional_edges("llm_tool_choice", should_continue, ["tool_call", END])

graph.add_edge(
start_key="tool_call",
end_key="basic_use_tool_response",
)

graph.add_edge(
start_key="basic_use_tool_response",
end_key=END,
)

return graph


def should_continue(state: BasicState) -> str:
return (
# If there are no tool calls, basic graph already streamed the answer
END
if state.tool_choice is None
else "tool_call"
)


if __name__ == "__main__":
from onyx.db.engine import get_session_context_manager
from onyx.context.search.models import SearchRequest
from onyx.llm.factory import get_default_llms
from onyx.agents.agent_search.shared_graph_utils.utils import get_test_config

graph = basic_graph_builder()
compiled_graph = graph.compile()
input = BasicInput(_unused=True)
primary_llm, fast_llm = get_default_llms()
with get_session_context_manager() as db_session:
config, _ = get_test_config(
db_session=db_session,
primary_llm=primary_llm,
fast_llm=fast_llm,
search_request=SearchRequest(query="How does onyx use FastAPI?"),
)
compiled_graph.invoke(input, config={"metadata": {"config": config}})
35 changes: 35 additions & 0 deletions backend/onyx/agents/agent_search/basic/states.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import TypedDict

from langchain_core.messages import AIMessageChunk
from pydantic import BaseModel

from onyx.agents.agent_search.orchestration.states import ToolCallUpdate
from onyx.agents.agent_search.orchestration.states import ToolChoiceInput
from onyx.agents.agent_search.orchestration.states import ToolChoiceUpdate

# States contain values that change over the course of graph execution,
# Config is for values that are set at the start and never change.
# If you are using a value from the config and realize it needs to change,
# you should add it to the state and use/update the version in the state.


## Graph Input State
class BasicInput(BaseModel):
# Langgraph needs a nonempty input, but we pass in all static
# data through a RunnableConfig.
_unused: bool = True


## Graph Output State
class BasicOutput(TypedDict):
tool_call_chunk: AIMessageChunk


## Graph State
class BasicState(
BasicInput,
ToolChoiceInput,
ToolCallUpdate,
ToolChoiceUpdate,
):
pass
63 changes: 63 additions & 0 deletions backend/onyx/agents/agent_search/basic/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from collections.abc import Iterator
from typing import cast

from langchain_core.messages import AIMessageChunk
from langchain_core.messages import BaseMessage
from langgraph.types import StreamWriter

from onyx.agents.agent_search.shared_graph_utils.utils import write_custom_event
from onyx.chat.models import LlmDoc
from onyx.chat.stream_processing.answer_response_handler import AnswerResponseHandler
from onyx.chat.stream_processing.answer_response_handler import CitationResponseHandler
from onyx.chat.stream_processing.answer_response_handler import (
PassThroughAnswerResponseHandler,
)
from onyx.chat.stream_processing.utils import map_document_id_order
from onyx.utils.logger import setup_logger

logger = setup_logger()


def process_llm_stream(
messages: Iterator[BaseMessage],
should_stream_answer: bool,
writer: StreamWriter,
final_search_results: list[LlmDoc] | None = None,
displayed_search_results: list[LlmDoc] | None = None,
) -> AIMessageChunk:
tool_call_chunk = AIMessageChunk(content="")

if final_search_results and displayed_search_results:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yuhongsun96 we're using a CitationResponseHandler here, but using other code to handle agent search citations. A little unfortunate

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably use the same style of objects at least, even if there is more than one of them for each case.

answer_handler: AnswerResponseHandler = CitationResponseHandler(
context_docs=final_search_results,
final_doc_id_to_rank_map=map_document_id_order(final_search_results),
display_doc_id_to_rank_map=map_document_id_order(displayed_search_results),
)
Comment on lines +30 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: CitationResponseHandler is only created if both results are non-None, but individual nulls aren't handled. Could cause issues if only one is None

Suggested change
if final_search_results and displayed_search_results:
answer_handler: AnswerResponseHandler = CitationResponseHandler(
context_docs=final_search_results,
final_doc_id_to_rank_map=map_document_id_order(final_search_results),
display_doc_id_to_rank_map=map_document_id_order(displayed_search_results),
)
if not (final_search_results and displayed_search_results):
answer_handler = PassThroughAnswerResponseHandler()
else:
answer_handler: AnswerResponseHandler = CitationResponseHandler(
context_docs=final_search_results,
final_doc_id_to_rank_map=map_document_id_order(final_search_results),
display_doc_id_to_rank_map=map_document_id_order(displayed_search_results),
)

else:
answer_handler = PassThroughAnswerResponseHandler()

full_answer = ""
# This stream will be the llm answer if no tool is chosen. When a tool is chosen,
# the stream will contain AIMessageChunks with tool call information.
for message in messages:
answer_piece = message.content
if not isinstance(answer_piece, str):
# this is only used for logging, so fine to
# just add the string representation
answer_piece = str(answer_piece)
full_answer += answer_piece

if isinstance(message, AIMessageChunk) and (
message.tool_call_chunks or message.tool_calls
):
tool_call_chunk += message # type: ignore
elif should_stream_answer:
for response_part in answer_handler.handle_response_part(message, []):
write_custom_event(
"basic_response",
response_part,
writer,
)

logger.debug(f"Full answer: {full_answer}")
return cast(AIMessageChunk, tool_call_chunk)
Loading
Loading