Source Nodes in FunctionAgent #20494
Replies: 3 comments 2 replies
|
FunctionAgent does not return Source Nodes by default because it doesn't transfer them from the tool output into the ChatMessage stored in memory, unlike OpenAIAgent. There's no built-in config to change this, but you can override FunctionAgent's handle_tool_call_results method to include source_nodes in the additional_kwargs of each ChatMessage. This preserves them for later use or inspection. Here's a minimal example: from llama_index.core.agent.workflow.function_agent import FunctionAgent
from llama_index.core.base.llms.types import ChatMessage
class EnhancedFunctionAgent(FunctionAgent):
async def handle_tool_call_results(self, ctx, results, memory):
scratchpad = await ctx.get(self.scratchpad_key, default=[])
for tool_call_result in results:
# Extract source_nodes from the tool output's raw_output
source_nodes = getattr(tool_call_result.tool_output.raw_output, "source_nodes", [])
scratchpad.append(
ChatMessage(
role="tool",
content=str(tool_call_result.tool_output.content),
additional_kwargs={
"tool_call_id": tool_call_result.tool_id,
"source_nodes": [n.model_dump() for n in source_nodes],
},
)
)
await ctx.set(self.scratchpad_key, scratchpad)With this change, source nodes will be included in the ChatMessage's additional_kwargs and can be accessed from memory after the agent run. For more details and discussion, see this issue and code example. Also, if you use return_direct=True on your QueryEngineTool, it can affect when and how source_nodes are accessible, so be mindful of that setting in your workflow reference. To reply, just mention @dosu. Share context across your team and agents. Try Dosu. |
|
FunctionAgent handles tool outputs differently than OpenAIAgent. Here is how to get source nodes: Approach 1: Extract from tool output from llama_index.core.agent import FunctionAgent
agent = FunctionAgent.from_tools(
tools=[query_engine_tool],
llm=llm,
verbose=True
)
response = agent.chat("What is X?")
# Access source nodes from the last tool call
if response.sources:
for source in response.sources:
print(source.node.text)Approach 2: Custom tool wrapper from llama_index.core.tools import FunctionTool
class TrackedQueryEngineTool:
def __init__(self, query_engine):
self.query_engine = query_engine
self.last_source_nodes = []
def query(self, query: str) -> str:
response = self.query_engine.query(query)
self.last_source_nodes = response.source_nodes
return str(response)
tracked_tool = TrackedQueryEngineTool(query_engine)
tool = FunctionTool.from_defaults(
fn=tracked_tool.query,
name="search",
description="Search documents"
)
# After agent call
print(tracked_tool.last_source_nodes)Approach 3: Use AgentRunner with callbacks from llama_index.core.callbacks import CallbackManager
callback_manager = CallbackManager([])
agent = FunctionAgent.from_tools(
tools=[query_engine_tool],
callback_manager=callback_manager
)We build RAG agents at Revolution AI — the custom tool wrapper gives you the most control over source node access. |
Uh oh!
There was an error while loading. Please reload this page.
Hello,
I was using OpenAIAgent, which returns Source Nodes when a tool of type QueryEngineTool is used.
After migrating to FunctionAgent, the Source Nodes are no longer returned.
Is there a way for FunctionAgent to return Source Nodes as well?
Thank you
All reactions