From 66d327d09c1d7963612cf96e067e2ae6004431e6 Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Sun, 13 Sep 2026 22:55:25 +0530 Subject: [PATCH 1/2] feat: add support for monitoring --- builders/workflow_builder.py | 5 +++++ extract.py | 2 ++ scrape.py | 3 +++ 3 files changed, 10 insertions(+) diff --git a/builders/workflow_builder.py b/builders/workflow_builder.py index 940e952..9588169 100644 --- a/builders/workflow_builder.py +++ b/builders/workflow_builder.py @@ -35,6 +35,11 @@ def navigate(self, url: str): self.current_step = main_step return self + def monitor_changes(self, enabled: bool = True): + """Compare each successful run with the previous successful run.""" + self.meta["compareRuns"] = enabled + return self + def click(self, selector: str): return self._add_action("click", [selector]) diff --git a/extract.py b/extract.py index fb44376..41754df 100644 --- a/extract.py +++ b/extract.py @@ -56,6 +56,7 @@ async def extract( llm_api_key: Optional[str] = None, llm_base_url: Optional[str] = None, robot_name: Optional[str] = None, + compare_runs: Optional[bool] = None, ) -> Robot: """Create an AI-powered extraction robot from a natural language prompt. @@ -77,6 +78,7 @@ async def extract( "url": url, **{key: value for key, value in llm_options.items() if value is not None}, "robotName": robot_name, + **({"compareRuns": compare_runs} if compare_runs is not None else {}), } robot_data = await self.client.extract_with_llm(options) robot = await self.client.get_robot(robot_data["robotId"]) diff --git a/scrape.py b/scrape.py index 99b1de1..cc3062c 100644 --- a/scrape.py +++ b/scrape.py @@ -22,6 +22,7 @@ async def create( llm_model: Optional[str] = None, llm_api_key: Optional[str] = None, llm_base_url: Optional[str] = None, + compare_runs: Optional[bool] = None, ) -> Robot: """ Create a scrape robot. @@ -46,6 +47,8 @@ async def create( } if smart_queries: meta["smartQueries"] = smart_queries.strip() + if compare_runs is not None: + meta["compareRuns"] = compare_runs meta.update(build_llm_payload(llm_provider, llm_model, llm_api_key, llm_base_url)) From 52945f0c935f17a916d830ceb29bd34040e5ae2b Mon Sep 17 00:00:00 2001 From: Rohit Rajan Date: Wed, 16 Sep 2026 18:18:48 +0530 Subject: [PATCH 2/2] fix: diff comparison logic --- .DS_Store | Bin 0 -> 6148 bytes builders/workflow_builder.py | 4 +- client.py | 37 ++++++++++++-- extract.py | 4 +- monitoring_test.py | 96 +++++++++++++++++++++++++++++++++++ robot.py | 8 +++ scrape.py | 8 +-- 7 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 .DS_Store create mode 100644 monitoring_test.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..fe25b54cfce10557c4b144f3c0247cf041a92a78 GIT binary patch literal 6148 zcmeHKy-EW?5S}$r4{by&EDs+*O_TZzmnZN6)clZy?Mrega*F@i@1XjabhN zQ1Kj{Mq#V{#Z)2+bg-CevL_ixUZ zlH;|ZV8$vj&SRb7yDd) zl2-8P(H>Q44Xp_-@VH!v9!>^Fy1d#43oqfd<~3kmm*daTE?-iM%-5p|ZR2<9Pz_8s zXfw&QoXd)FIqsicJr*xD$JAUdyJ2w&UJy^$tkW(OjPSJsX5V-{#aS^fulCVxbZ`Do z9`X}&xx9wOC3vYblob0x0qogq-lRe4MFCMj6!=tt*9RYsF|ZgL)Jq3CeFOmJ;ns$0 zK1* Robot: """Create an AI-powered extraction robot from a natural language prompt. @@ -78,7 +78,7 @@ async def extract( "url": url, **{key: value for key, value in llm_options.items() if value is not None}, "robotName": robot_name, - **({"compareRuns": compare_runs} if compare_runs is not None else {}), + **({"monitor": monitor} if monitor is not None else {}), } robot_data = await self.client.extract_with_llm(options) robot = await self.client.get_robot(robot_data["robotId"]) diff --git a/monitoring_test.py b/monitoring_test.py new file mode 100644 index 0000000..b658c3c --- /dev/null +++ b/monitoring_test.py @@ -0,0 +1,96 @@ +import asyncio +import os + +from dotenv import load_dotenv +from maxun import Config, Scrape + +load_dotenv() + + +async def main(): + api_key = os.environ.get("MAXUN_API_KEY") + base_url = os.environ.get( + "MAXUN_BASE_URL", + "http://localhost:8080/api/sdk", + ) + + if not api_key: + raise RuntimeError("MAXUN_API_KEY is required") + + scrape = Scrape( + Config( + api_key=api_key, + base_url=base_url, + ) + ) + + try: + robot = await scrape.create( + name="Python SDK Monitoring Test", + url="https://www.worldometers.info/world-population/", + formats=["text", "markdown", "html"], + monitor=True, + ) + + print(f"Robot created: {robot.id}") + + print("\nRunning baseline capture...") + baseline = await robot.run() + + print( + { + "runId": baseline.get("runId"), + "hasChanges": baseline.get("hasChanges"), + "changedFormats": baseline.get("changedFormats"), + } + ) + + print("\nRunning comparison capture...") + current = await robot.run() + + print( + { + "runId": current.get("runId"), + "hasChanges": current.get("hasChanges"), + "changedFormats": current.get("changedFormats"), + } + ) + + comparison = await robot.get_run_diff(current["runId"]) + + print( + "\nComparison:", + { + "previousRunId": comparison.get("previousRunId"), + "currentRunId": comparison.get("runId"), + "hasChanges": comparison.get("hasChanges"), + "changedFormats": comparison.get("changedFormats"), + }, + ) + + for format_diff in comparison.get("diffs", []): + print(f"\n--- {format_diff['format']} ---") + + printed = 0 + + for change in format_diff.get("changes", []): + if not change.get("added") and not change.get("removed"): + continue + + prefix = "+" if change.get("added") else "-" + value = change.get("value", "") + + # Keep terminal output manageable, especially for HTML. + print(f"{prefix} {value[:1000]}") + printed += 1 + + if printed >= 20: + print("... remaining changes omitted") + break + + finally: + await scrape.client.client.aclose() + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/robot.py b/robot.py index 4cb908f..d7211ee 100644 --- a/robot.py +++ b/robot.py @@ -28,6 +28,14 @@ async def get_runs(self) -> list: async def get_run(self, run_id: str) -> dict: return await self.client.get_run(self.id, run_id) + async def get_run_diff( + self, + run_id: str, + format: Optional[str] = None, + ) -> dict: + """Return the detailed monitoring diff for a completed run.""" + return await self.client.get_run_diff(self.id, run_id, format) + async def get_latest_run(self) -> Optional[dict]: runs = await self.get_runs() if not runs: diff --git a/scrape.py b/scrape.py index cc3062c..121ae2c 100644 --- a/scrape.py +++ b/scrape.py @@ -22,7 +22,7 @@ async def create( llm_model: Optional[str] = None, llm_api_key: Optional[str] = None, llm_base_url: Optional[str] = None, - compare_runs: Optional[bool] = None, + monitor: Optional[bool] = None, ) -> Robot: """ Create a scrape robot. @@ -47,8 +47,8 @@ async def create( } if smart_queries: meta["smartQueries"] = smart_queries.strip() - if compare_runs is not None: - meta["compareRuns"] = compare_runs + if monitor is not None: + meta["monitor"] = monitor meta.update(build_llm_payload(llm_provider, llm_model, llm_api_key, llm_base_url)) @@ -61,4 +61,4 @@ async def create( return Robot(self.client, robot_data) def _random_string(self, length: int = 9) -> str: - return "".join(random.choices(string.ascii_lowercase + string.digits, k=length)) \ No newline at end of file + return "".join(random.choices(string.ascii_lowercase + string.digits, k=length))