-
Notifications
You must be signed in to change notification settings - Fork 10
Add CI lint for Python code examples #364
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
Changes from all commits
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 |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| name: Lint Code Examples | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - 'pages/**/*.mdx' | ||
| - 'scripts/lint-code-examples.py' | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - 'pages/**/*.mdx' | ||
| - 'scripts/lint-code-examples.py' | ||
|
|
||
| jobs: | ||
| lint-code-examples: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.12' | ||
| - run: pip install genvm-linter | ||
| - run: python scripts/lint-code-examples.py | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ When crafting prompts for LLMs, it's important to use a format that clearly and | |
| In the example **Wizard of Coin** contract below, we want the LLM to decide whether the wizard should give the coin to an adventurer. | ||
|
|
||
| ```python | ||
| # { "Depends": "py-genlayer:test" } | ||
| # { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } | ||
| from genlayer import * | ||
|
|
||
| import json | ||
|
|
@@ -55,13 +55,14 @@ This result should be perfectly parseable by a JSON parser without errors. | |
| """ | ||
|
|
||
| def nondet(): | ||
| res = gl.exec_prompt(prompt) | ||
| res = res.replace("```json", "").replace("```", "") | ||
| res = gl.nondet.exec_prompt(prompt) | ||
| backticks = "``" + "`" | ||
| res = res.replace(backticks + "json", "").replace(backticks, "") | ||
|
Comment on lines
+58
to
+60
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
python - <<'PY'
import json
def current(res: str) -> str:
backticks = "``" + "`"
return res.replace(backticks + "json", "").replace(backticks, "")
samples = {
"lowercase json": "```json\n{\"give_coin\": false}\n```",
"uppercase JSON": "```JSON\n{\"give_coin\": false}\n```",
"space after fence": "``` json\n{\"give_coin\": false}\n```",
"bare fence": "```\n{\"give_coin\": false}\n```",
}
for name, sample in samples.items():
cleaned = current(sample)
try:
json.loads(cleaned)
verdict = "parses"
except Exception as exc:
verdict = f"fails: {exc}"
print(f"{name}: {verdict}")
PYRepository: genlayerlabs/genlayer-docs Length of output: 244 🏁 Script executed: fd crafting-prompts.mdx --type fRepository: genlayerlabs/genlayer-docs Length of output: 129 🏁 Script executed: cat -n pages/developers/intelligent-contracts/crafting-prompts.mdx | sed -n '50,70p'Repository: genlayerlabs/genlayer-docs Length of output: 953 Strip only surrounding code fences before The current implementation only handles the exact lowercase
This will cause the documented example to fail at runtime on typical LLM outputs. Proposed fix def nondet():
res = gl.nondet.exec_prompt(prompt)
- backticks = "``" + "`"
- res = res.replace(backticks + "json", "").replace(backticks, "")
+ res = res.strip()
+ if res.startswith("```"):
+ lines = res.splitlines()
+ if lines:
+ lines = lines[1:]
+ if lines and lines[-1].strip() == "```":
+ lines = lines[:-1]
+ res = "\n".join(lines).strip()
print(res)
dat = json.loads(res)🤖 Prompt for AI Agents |
||
| print(res) | ||
| dat = json.loads(res) | ||
| return dat["give_coin"] | ||
|
|
||
| result = gl.eq_principle_strict_eq(nondet) | ||
| result = gl.eq_principle.strict_eq(nondet) | ||
| assert isinstance(result, bool) | ||
| self.have_coin = result | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| The FetchGitHubProfile contract demonstrates how to fetch and store GitHub profile content within an intelligent contract. This contract shows how to use the [comparative equivalence principle](/developers/intelligent-contracts/equivalence-principle#comparative-equivalence-principle) to ensure all nodes agree on the same profile content. | ||
|
|
||
| ```python | ||
| # { "Depends": "py-genlayer:test" } | ||
| # { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } | ||
|
|
||
| from genlayer import * | ||
| import typing | ||
|
|
@@ -20,9 +20,10 @@ class FetchGitHubProfile(gl.Contract): | |
| github_profile_url = "https://github.com/"+github_handle | ||
|
|
||
| def fetch_github_profile_page_content() -> str: | ||
| return gl.get_webpage(github_profile_url, mode="text") | ||
|
|
||
| self.github_profile = gl.eq_principle_strict_eq(fetch_github_profile_page_content) | ||
| response = gl.nondet.web.get(github_profile_url) | ||
| return response.body.decode("utf-8") | ||
|
|
||
| self.github_profile = gl.eq_principle.strict_eq(fetch_github_profile_page_content) | ||
|
|
||
| @gl.public.view | ||
| def show_github_profile(self) -> str: | ||
|
|
@@ -35,14 +36,14 @@ class FetchGitHubProfile(gl.Contract): | |
| - **Write Method**: | ||
| - `fetch_github_profile(github_handle)` takes a GitHub username and retrieves their profile content. | ||
| - Constructs the profile URL using the provided handle. | ||
| - Uses `gl.eq_principle_strict_eq()` to ensure all nodes agree on the same profile content. | ||
| - Uses `gl.eq_principle.strict_eq()` to ensure all nodes agree on the same profile content. | ||
| - **Read Method**: | ||
| - `show_github_profile()` returns the stored profile content. | ||
|
|
||
| ## Key Components | ||
|
|
||
| 1. **GitHub Integration**: The contract uses `gl.get_webpage()` to fetch content from GitHub profiles. | ||
| 2. **Deterministic Execution**: `gl.eq_principle_strict_eq()` ensures that all nodes in the network arrive at the same exact content. | ||
| 1. **GitHub Integration**: The contract uses `gl.nondet.web.get()` to fetch content from GitHub profiles. | ||
| 2. **Deterministic Execution**: `gl.eq_principle.strict_eq()` ensures that all nodes in the network arrive at the same exact content. | ||
|
Comment on lines
+39
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Finish the terminology update across the whole page. This section now documents 🧰 Tools🪛 LanguageTool[style] ~46-~46: This phrase is redundant. Consider writing “same”. (SAME_EXACT) 🤖 Prompt for AI Agents |
||
| 3. **State Management**: The contract maintains a single string state variable that stores the profile content. | ||
|
|
||
| ## Deploying the Contract | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| The FetchWebContent contract demonstrates how to fetch and store web content within an intelligent contract. This contract shows how to use the [comparative equivalence principle](/developers/intelligent-contracts/equivalence-principle#comparative-equivalence-principle) to ensure all nodes agree on the same web content. | ||
|
|
||
| ```python | ||
| # { "Depends": "py-genlayer:test" } | ||
| # { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } | ||
|
|
||
| from genlayer import * | ||
| import typing | ||
|
|
@@ -19,9 +19,10 @@ class FetchWebContent(gl.Contract): | |
| def fetch_web_content(self) -> typing.Any: | ||
|
|
||
| def fetch_web_url_content() -> str: | ||
| return gl.get_webpage("https://example.com/", mode="text") | ||
|
|
||
| self.content = gl.eq_principle_strict_eq(fetch_web_url_content) | ||
| response = gl.nondet.web.get("https://example.com/") | ||
| return response.body.decode("utf-8") | ||
|
|
||
| self.content = gl.eq_principle.strict_eq(fetch_web_url_content) | ||
|
|
||
| @gl.public.view | ||
| def show_content(self) -> str: | ||
|
|
@@ -33,15 +34,15 @@ class FetchWebContent(gl.Contract): | |
| - **Initialization**: The `FetchWebContent` class initializes with an empty string in the `content` variable. | ||
| - **Write Method**: | ||
| - `fetch_web_content()` retrieves content from a web page and stores it. | ||
| - It contains an inner function `fetch_web_url_content()` that uses `gl.get_webpage()` to fetch content. | ||
| - Uses `gl.eq_principle_strict_eq()` to ensure all nodes agree on the same content. | ||
| - It contains an inner function `fetch_web_url_content()` that uses `gl.nondet.web.get()` to fetch content. | ||
| - Uses `gl.eq_principle.strict_eq()` to ensure all nodes agree on the same content. | ||
| - **Read Method**: | ||
| - `show_content()` returns the stored web content. | ||
|
|
||
| ## Key Components | ||
|
|
||
| 1. **Web Integration**: The contract uses `gl.get_webpage()` to fetch content from web URLs. | ||
| 2. **Deterministic Execution**: `gl.eq_principle_strict_eq()` ensures that all nodes in the network arrive at the same exact content. | ||
| 1. **Web Integration**: The contract uses `gl.nondet.web.get()` to fetch content from web URLs. | ||
| 2. **Deterministic Execution**: `gl.eq_principle.strict_eq()` ensures that all nodes in the network arrive at the same exact content. | ||
| 3. **State Management**: The contract maintains a single string state variable that stores the web content. | ||
|
|
||
| ## Deploying the Contract | ||
|
|
@@ -101,7 +102,7 @@ You can monitor the contract's behavior through transaction logs, which will sho | |
|
|
||
| ## HTML Mode for Web Content | ||
|
|
||
| The `gl.get_webpage()` function supports different modes for retrieving web content. While `mode="text"` returns the plain text content, `mode="html"` allows you to retrieve the complete HTML `<body>` of the webpage. | ||
| The `gl.nondet.web.get()` function supports different modes for retrieving web content. While the default mode returns the plain text content, `gl.nondet.web.render()` with `mode="html"` allows you to retrieve the complete HTML `<body>` of the webpage. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result:
Sources: 🏁 Script executed: cat -n pages/developers/intelligent-contracts/examples/fetch-web-content.mdx | sed -n '85,135p'Repository: genlayerlabs/genlayer-docs Length of output: 2433 Fix conflicting API documentation about mode parameters and HTML output scope. The file contains two contradictory statements:
Update Line 92 to accurately reflect which functions support mode parameters, and align Lines 105 and 130 to consistently describe what 🤖 Prompt for AI Agents |
||
|
|
||
| Here's an example of using HTML mode: | ||
|
|
||
|
|
@@ -115,9 +116,9 @@ class FetchHTMLContent(gl.Contract): | |
| @gl.public.write | ||
| def fetch_html_content(self) -> typing.Any: | ||
| def fetch_web_url_html() -> str: | ||
| return gl.get_webpage("https://example.com/", mode="html") | ||
| self.html_content = gl.eq_principle_strict_eq(fetch_web_url_html) | ||
| return gl.nondet.web.render("https://example.com/", mode='html') | ||
|
|
||
| self.html_content = gl.eq_principle.strict_eq(fetch_web_url_html) | ||
|
|
||
| @gl.public.view | ||
| def show_html_content(self) -> str: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| The Prediction Market contract sets up a scenario to determine the outcome of a football game between two teams. The contract uses the Equivalence Principle to ensure accurate and consistent decision-making based on the game's resolution data. | ||
|
|
||
| ```python filename="PredictionMarket" copy | ||
| # { "Depends": "py-genlayer:test" } | ||
| # { "Depends": "py-genlayer:1jb45aa8ynh2a9c9xn3b7qqh8sm5q93hwfp7jqmwsfhh8jpz09h6" } | ||
|
|
||
| from genlayer import * | ||
|
|
||
|
|
@@ -49,7 +49,8 @@ class PredictionMarket(gl.Contract): | |
| return "Already resolved" | ||
|
|
||
| def nondet() -> str: | ||
| web_data = gl.get_webpage(self.resolution_url, mode="text") | ||
| response = gl.nondet.web.get(self.resolution_url) | ||
| web_data = response.body.decode("utf-8") | ||
|
Comment on lines
+52
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result:
Sources: [1] Use Lines 52-53 use Suggested change:response = gl.nondet.web.render(self.resolution_url, mode='text')
web_data = response🤖 Prompt for AI Agents |
||
| print(web_data) | ||
|
|
||
| task = f"""In the following web page, find the winning team in a matchup between the following teams: | ||
|
|
@@ -73,11 +74,11 @@ class PredictionMarket(gl.Contract): | |
| your output must be only JSON without any formatting prefix or suffix. | ||
| This result should be perfectly parsable by a JSON parser without errors. | ||
| """ | ||
| result = gl.exec_prompt(task).replace("```json", "").replace("```", "") | ||
| result = gl.nondet.exec_prompt(task).replace("```json", "").replace("```", "") | ||
| print(result) | ||
| return json.dumps(json.loads(result), sort_keys=True) | ||
|
|
||
| result_json = json.loads(gl.eq_principle_strict_eq(nondet)) | ||
| result_json = json.loads(gl.eq_principle.strict_eq(nondet)) | ||
|
|
||
| if result_json["winner"] > -1: | ||
| self.has_resolved = True | ||
|
|
||
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.
Include this workflow file in its own path filters.
A PR that only changes
.github/workflows/lint-code-examples.ymlwill not run this job today, so workflow regressions can merge without ever exercising the updated definition.Suggested fix
on: pull_request: paths: + - '.github/workflows/lint-code-examples.yml' - 'pages/**/*.mdx' - 'scripts/lint-code-examples.py' push: branches: [main] paths: + - '.github/workflows/lint-code-examples.yml' - 'pages/**/*.mdx' - 'scripts/lint-code-examples.py'📝 Committable suggestion
🤖 Prompt for AI Agents