Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 27 additions & 7 deletions integuru/util/print.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,15 +172,35 @@ def generate_code(node_id: str, graph: nx.DiGraph) -> str:
key_path = find_json_path(json.loads(response_text), extracted_part)
key_paths.append(key_path)

parse_response_prompt = f"""
Response:
{response_text}
# Large JSON API responses (bulk data dumps, paginated listings, etc.)
# can easily be hundreds of KB. Embedding them in full blows past the
# model's context window (see issue #30). The HTML/JS branch above
# already guards against this; mirror the same threshold here. The
# key_paths computed above already pinpoint exactly where each
# variable lives in the parsed JSON, so they're sufficient on their
# own to write the extraction code without the raw response text.
if len(response_text) > 100000:
parse_response_prompt = f"""
The JSON response is too large to include in full ({len(response_text)} characters).

Parse out the following variables from the response using JSON keys:
{key_paths}
Each variable to extract has already been located in the parsed JSON as a key path
(the sequence of keys/indices from the root of the response to reach that value):
{key_paths}

Through your judgement from analyzing the response, if polling is required to retrieve the variables above from the response. If so, implement polling else dont.
"""
Parse out these variables by navigating the parsed JSON response using the key paths above.

Through your judgement from analyzing the key paths, if polling is required to retrieve the variables above from the response. If so, implement polling else dont.
"""
else:
parse_response_prompt = f"""
Response:
{response_text}

Parse out the following variables from the response using JSON keys:
{key_paths}

Through your judgement from analyzing the response, if polling is required to retrieve the variables above from the response. If so, implement polling else dont.
"""

if "text/html" in response_type or "application/javascript" in response_type:
if len(response_text) > 100000:
Expand Down
82 changes: 82 additions & 0 deletions tests/test_generate_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import json
import unittest
from unittest.mock import patch, MagicMock

import networkx as nx

from integuru.util.print import generate_code


def _build_graph(response_text: str, response_type: str = "application/json"):
graph = nx.DiGraph()
request = MagicMock()
request.__str__.return_value = "curl 'https://example.com/api/data'"
graph.add_node(
"node_1",
node_type="curl",
content={
"key": request,
"value": {"type": response_type, "text": response_text},
},
dynamic_parts=[],
extracted_parts=["needle-value"],
input_variables=[],
)
return graph


class TestGenerateCodeJsonSizeGuard(unittest.TestCase):
"""
Regression coverage for https://github.com/Integuru-AI/Integuru/issues/30:
embedding an oversized JSON API response verbatim in the code-generation
prompt can exceed the model's context window. generate_code() already
guarded the HTML/JS response branch against this (a >100000 character
threshold); this covers the same guard now applied to the JSON branch.
"""

@patch("integuru.util.print.llm")
def test_large_json_response_is_not_embedded_in_full(self, mock_llm):
# given a JSON response so large it would blow past a typical
# 128k-token context window if embedded verbatim
large_value = "x" * 200_000
response_text = json.dumps({"data": {"id": "needle-value"}, "blob": large_value})
graph = _build_graph(response_text)

mock_response = MagicMock()
mock_response.content = "def fetch_data(cookie_string):\n pass"
mock_llm.switch_to_alternate_model.return_value.invoke.return_value = mock_response

# when
generate_code("node_1", graph)

# then the prompt sent to the model must not contain the oversized
# raw response text, but must still contain the resolved key path so
# the model can navigate the parsed JSON itself
sent_prompt = mock_llm.switch_to_alternate_model.return_value.invoke.call_args[0][0]
self.assertNotIn(large_value, sent_prompt)
self.assertIn("too large to include in full", sent_prompt)
self.assertIn("'data'", sent_prompt)
self.assertIn("'id'", sent_prompt)

@patch("integuru.util.print.llm")
def test_small_json_response_is_still_embedded(self, mock_llm):
# given a small JSON response (the common case), behavior is
# unchanged: the full response text is still included
response_text = json.dumps({"data": {"id": "needle-value"}})
graph = _build_graph(response_text)

mock_response = MagicMock()
mock_response.content = "def fetch_data(cookie_string):\n pass"
mock_llm.switch_to_alternate_model.return_value.invoke.return_value = mock_response

# when
generate_code("node_1", graph)

# then
sent_prompt = mock_llm.switch_to_alternate_model.return_value.invoke.call_args[0][0]
self.assertIn("needle-value", sent_prompt)
self.assertNotIn("too large to include in full", sent_prompt)


if __name__ == "__main__":
unittest.main()