Extract numerical context features for the classifier. These encode agentic context as numerical signals that the classifier can learn from — no keyword matching, no binary overrides.
(body: dict, step_type: str, prompt: str = "")
| 1837 | |
| 1838 | |
| 1839 | def extract_context_features(body: dict, step_type: str, prompt: str = "") -> dict[str, float]: |
| 1840 | """Extract numerical context features for the classifier. |
| 1841 | |
| 1842 | These encode agentic context as numerical signals that the classifier |
| 1843 | can learn from — no keyword matching, no binary overrides. |
| 1844 | """ |
| 1845 | messages = body.get("messages", []) |
| 1846 | raw_tools = body.get("tools") or body.get("customTools") or [] |
| 1847 | |
| 1848 | tool_count = len(raw_tools) |
| 1849 | tools_present = 1.0 if tool_count > 0 else 0.0 |
| 1850 | last_role_tool = 1.0 if step_type == "tool-result-followup" else 0.0 |
| 1851 | conversation_depth = min(1.0, len(messages) / 30.0) |
| 1852 | |
| 1853 | tool_result_length = 0.0 |
| 1854 | prior_tool_calls = 0 |
| 1855 | for msg in messages: |
| 1856 | role = msg.get("role", "") |
| 1857 | tool_result_text, _ = _latest_tool_result_signal([msg]) |
| 1858 | if tool_result_text: |
| 1859 | tool_result_length = max(tool_result_length, len(tool_result_text)) |
| 1860 | if role == "tool": |
| 1861 | prior_tool_calls += 1 |
| 1862 | elif role == "assistant" and msg.get("tool_calls"): |
| 1863 | prior_tool_calls += 1 |
| 1864 | |
| 1865 | user_after_tools = 0.0 |
| 1866 | saw_tool = False |
| 1867 | for msg in messages: |
| 1868 | if msg.get("role") == "tool" or _latest_tool_result_signal([msg])[0]: |
| 1869 | saw_tool = True |
| 1870 | elif msg.get("role") == "user" and saw_tool: |
| 1871 | user_after_tools = 1.0 |
| 1872 | |
| 1873 | prompt_ratio = 0.5 |
| 1874 | if messages: |
| 1875 | total_len = sum(len(str(msg.get("content", ""))) for msg in messages) |
| 1876 | if total_len > 0: |
| 1877 | prompt_ratio = min(1.0, len(prompt) / total_len) |
| 1878 | |
| 1879 | return { |
| 1880 | "ctx_tools_present": tools_present, |
| 1881 | "ctx_tool_count": min(1.0, tool_count / 20.0), |
| 1882 | "ctx_last_role_tool": last_role_tool, |
| 1883 | "ctx_tool_result_length": min(1.0, tool_result_length / 3000.0), |
| 1884 | "ctx_conversation_depth": conversation_depth, |
| 1885 | "ctx_prior_tool_calls": min(1.0, prior_tool_calls / 10.0), |
| 1886 | "ctx_user_after_tools": user_after_tools, |
| 1887 | "ctx_prompt_ratio": prompt_ratio, |
| 1888 | } |
| 1889 | |
| 1890 | |
| 1891 | def _extract_requirements(body: dict, step_type: str, prompt: str = "") -> tuple[RequestRequirements, WorkloadHints]: |