|
18 | 18 | ) |
19 | 19 | from haystack.components.agents.state.state_utils import merge_lists |
20 | 20 | from haystack.components.agents.tool_calling import _run_tool, _run_tool_async |
| 21 | +from haystack.components.agents.utils import _record_context_tokens |
21 | 22 | from haystack.components.builders import ChatPromptBuilder |
22 | 23 | from haystack.components.generators.chat.types import ChatGenerator |
23 | 24 | from haystack.core.serialization import component_to_dict, default_from_dict, default_to_dict |
|
82 | 83 | # - `continue_run`: set by an `on_exit` hook to keep the Agent running instead of stopping (re-read each exit attempt). |
83 | 84 | # - `tools`: the flattened tools available in the current step, so a hook can inspect them (e.g. HITL confirmation). |
84 | 85 | # - `hook_context`: per-run request-scoped resources passed to `run`/`run_async` for hooks to read. |
| 86 | +# - `context_tokens`: approximate current context-window size, refreshed after each LLM call, for hooks to read |
| 87 | +# (e.g. a `before_llm` hook that triggers compaction once the context grows too large). Kept internal rather than |
| 88 | +# exposed as an output because it is a best-effort snapshot; see `_record_context_tokens`. |
85 | 89 | _INTERNAL_STATE_KEYS: dict[str, dict[str, Any]] = { |
86 | 90 | "continue_run": {"type": bool, "handler": replace_values}, |
87 | 91 | "tools": {"type": list, "handler": replace_values}, |
88 | 92 | "hook_context": {"type": dict[str, Any], "handler": replace_values}, |
| 93 | + "context_tokens": {"type": int, "handler": replace_values}, |
89 | 94 | } |
90 | 95 |
|
91 | 96 |
|
@@ -938,6 +943,7 @@ def _initialize_fresh_execution( |
938 | 943 | state.set("messages", messages) |
939 | 944 | state.set("step_count", 0) |
940 | 945 | state.set("token_usage", {}) |
| 946 | + state.set("context_tokens", 0) |
941 | 947 | state.set("tool_call_counts", {tool.name: 0 for tool in flat_tools}) |
942 | 948 | state.set("exit_reason", None) |
943 | 949 | state.set("continue_run", False) |
@@ -1187,6 +1193,7 @@ def _run_step(self, exe_context: _ExecutionContext, agent_span: tracing.Span) -> |
1187 | 1193 | llm_messages = result["replies"] |
1188 | 1194 | exe_context.state.set("messages", llm_messages) |
1189 | 1195 | _record_llm_usage(exe_context.state, llm_messages) |
| 1196 | + _record_context_tokens(exe_context.state, llm_messages) |
1190 | 1197 |
|
1191 | 1198 | # Stop on the "no tool call" exit: no tools available, or a plain assistant text reply (see _is_text_exit). |
1192 | 1199 | if not current_tools or _is_text_exit(llm_messages): |
@@ -1251,6 +1258,7 @@ async def _run_step_async(self, exe_context: _ExecutionContext, agent_span: trac |
1251 | 1258 | llm_messages = result["replies"] |
1252 | 1259 | exe_context.state.set("messages", llm_messages) |
1253 | 1260 | _record_llm_usage(exe_context.state, llm_messages) |
| 1261 | + _record_context_tokens(exe_context.state, llm_messages) |
1254 | 1262 |
|
1255 | 1263 | # Stop on the "no tool call" exit: no tools available, or a plain assistant text reply (see _is_text_exit). |
1256 | 1264 | if not current_tools or _is_text_exit(llm_messages): |
|
0 commit comments