-
Notifications
You must be signed in to change notification settings - Fork 423
Inject agent session info into context #1471
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -123,6 +123,7 @@ description: Analyze CSV/Excel files | |
| | **ReadFile** | Read files with line numbers | `read_file("skills/data-analysis/config.json")` | | ||
| | **WriteFile** | Create/overwrite files | `write_file("outputs/report.pdf", data)` | | ||
| | **EditFile** | Precise string replacements | `edit_file("script.py", old="x", new="y")` | | ||
| | **SessionInfo**| Auto-injects session details into system instructions | *(automatic — no tool call needed)* | | ||
|
||
|
|
||
| ### Working Directory Structure | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||
|
|
||||||||||||||||||||
| import logging | ||||||||||||||||||||
| from typing import TYPE_CHECKING | ||||||||||||||||||||
|
|
||||||||||||||||||||
| from google.adk.agents import BaseAgent, LlmAgent | ||||||||||||||||||||
| from google.adk.tools.base_tool import BaseTool | ||||||||||||||||||||
| from google.adk.tools.tool_context import ToolContext | ||||||||||||||||||||
| from typing_extensions import override | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if TYPE_CHECKING: | ||||||||||||||||||||
| from google.adk.models.llm_request import LlmRequest | ||||||||||||||||||||
|
|
||||||||||||||||||||
| logger = logging.getLogger("kagent_adk." + __name__) | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| def add_session_tool(agent: BaseAgent) -> None: | ||||||||||||||||||||
| if not isinstance(agent, LlmAgent): | ||||||||||||||||||||
| return | ||||||||||||||||||||
| existing_tool_names = {getattr(t, "name", None) for t in agent.tools} | ||||||||||||||||||||
| if "get_session_info" not in existing_tool_names: | ||||||||||||||||||||
| agent.tools.append(SessionInfoTool()) | ||||||||||||||||||||
| logger.debug(f"Added session info tool to agent: {agent.name}") | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
|
||||||||||||||||||||
| class SessionInfoTool(BaseTool): | ||||||||||||||||||||
| """Tool for retrieving information about the current agent session.""" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| def __init__(self): | ||||||||||||||||||||
| super().__init__( | ||||||||||||||||||||
| name="get_session_info", | ||||||||||||||||||||
| description="Get information about the current agent session, including the session ID.", | ||||||||||||||||||||
| ) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @override | ||||||||||||||||||||
| async def process_llm_request( | ||||||||||||||||||||
| self, | ||||||||||||||||||||
| *, | ||||||||||||||||||||
| tool_context: ToolContext, | ||||||||||||||||||||
| llm_request: LlmRequest, | ||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||
| session = tool_context.session | ||||||||||||||||||||
| if not session: | ||||||||||||||||||||
| return | ||||||||||||||||||||
| info = ( | ||||||||||||||||||||
| "kagent session:\n" | ||||||||||||||||||||
| f"- session_id: {session.id or 'N/A'}\n" | ||||||||||||||||||||
| f"- user_id: {session.user_id or 'N/A'}\n" | ||||||||||||||||||||
|
Comment on lines
+45
to
+48
|
||||||||||||||||||||
| info = ( | |
| "kagent session:\n" | |
| f"- session_id: {session.id or 'N/A'}\n" | |
| f"- user_id: {session.user_id or 'N/A'}\n" | |
| user_id_display = "[redacted]" if getattr(session, "user_id", None) else "N/A" | |
| info = ( | |
| "kagent session:\n" | |
| f"- session_id: {session.id or 'N/A'}\n" | |
| f"- user_id: {user_id_display}\n" |
Copilot
AI
Mar 10, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SessionInfoTool injects instructions on every process_llm_request call. ADK can call process_llm_request multiple times in the same user turn (e.g., sequential tool round-trips), so this will duplicate the session info in system instructions and waste tokens. Consider guarding with a tool_context.state key (similar to PrefetchMemoryTool) or checking whether the info has already been appended before adding again.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ | |
| from google.adk.tools import BaseTool | ||
| from google.adk.tools.base_toolset import BaseToolset | ||
|
|
||
| from ..tools import BashTool, EditFileTool, ReadFileTool, WriteFileTool | ||
| from ..tools import BashTool, EditFileTool, ReadFileTool, SessionInfoTool, WriteFileTool | ||
| from .skill_tool import SkillsTool | ||
|
|
||
| logger = logging.getLogger("kagent_adk." + __name__) | ||
|
|
@@ -28,6 +28,7 @@ class SkillsToolset(BaseToolset): | |
| 3. WriteFileTool - Write/create files | ||
| 4. EditFileTool - Edit files with precise replacements | ||
| 5. BashTool - Execute shell commands | ||
| 6. SessionInfoTool - Inject session information into system instructions | ||
|
|
||
| Skills provide specialized domain knowledge and scripts that the agent can use | ||
| to solve complex tasks. The toolset enables discovery of available skills, | ||
|
|
@@ -51,18 +52,20 @@ def __init__(self, skills_directory: str | Path): | |
| self.write_file_tool = WriteFileTool() | ||
| self.edit_file_tool = EditFileTool() | ||
| self.bash_tool = BashTool(skills_directory) | ||
| self.session_info_tool = SessionInfoTool() | ||
|
|
||
| @override | ||
| async def get_tools(self, readonly_context: Optional[ReadonlyContext] = None) -> List[BaseTool]: | ||
| """Get all skills tools. | ||
|
|
||
| Returns: | ||
| List containing all skills tools: skills, read, write, edit, and bash. | ||
| List containing all skills tools: skills, read, write, edit, bash, and session info. | ||
| """ | ||
| return [ | ||
| self.skills_tool, | ||
| self.read_file_tool, | ||
| self.write_file_tool, | ||
| self.edit_file_tool, | ||
| self.bash_tool, | ||
| self.session_info_tool, | ||
|
Comment on lines
54
to
+70
|
||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from unittest.mock import Mock | ||
|
|
||
| import pytest | ||
| from google.adk.models.llm_request import LlmRequest | ||
|
|
||
| from kagent.adk.tools.session_tool import SessionInfoTool | ||
|
|
||
|
|
||
| class TestSessionInfoTool: | ||
| @pytest.mark.asyncio | ||
| async def test_session_info_tool(self): | ||
| tool = SessionInfoTool() | ||
|
|
||
| context = Mock() | ||
| context.session = Mock() | ||
| context.session.id = "session-123" | ||
| context.session.user_id = "user-456" | ||
| context.session.app_name = "test-app" | ||
|
|
||
| llm_request = LlmRequest() | ||
| await tool.process_llm_request(tool_context=context, llm_request=llm_request) | ||
|
|
||
| assert "session-123" in llm_request.config.system_instruction | ||
| assert "user-456" in llm_request.config.system_instruction | ||
| assert "test-app" in llm_request.config.system_instruction | ||
| assert "get_session_info" not in llm_request.tools_dict | ||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_session_info_tool_none_session(self): | ||
| tool = SessionInfoTool() | ||
|
|
||
| context = Mock() | ||
| context.session = None | ||
|
|
||
| llm_request = LlmRequest() | ||
| await tool.process_llm_request(tool_context=context, llm_request=llm_request) | ||
|
|
||
| assert llm_request.config.system_instruction is None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add_session_tool()is now invoked unconditionally for all CLI entrypoints. Since this changes the prompt for every request (and may expose identifiers), consider gating it behind configuration (e.g., AgentConfig/CRD field or an env var likeKAGENT_INJECT_SESSION_INFO=true) so operators can disable it in sensitive or token-constrained deployments.