From 5ea39b263a3db337fabe179139baf30b4e3153fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=84=E3=83=B3=E3=83=87=E3=83=AC?= Date: Mon, 23 Mar 2026 19:49:06 +0200 Subject: [PATCH] chore: Fix linting errors for CI --- .github/PULL_REQUEST_TEMPLATE.md | 10 ++-- CONTRIBUTING.md | 2 +- README.md | 2 +- docs/usage/claude.md | 4 +- docs/usage/gemini.md | 6 +-- docs/usage/ollama.md | 12 ++--- examples/ollama_skills_test.py | 45 +++++++++++------- flake8_report.txt | Bin 0 -> 7616 bytes .../prompt_rewriter/instructions.md | 2 +- skillware/core/loader.py | 6 +-- tests/skills/finance/test_wallet_screening.py | 35 +++++++++----- tests/test_loader.py | 6 ++- 12 files changed, 78 insertions(+), 52 deletions(-) create mode 100644 flake8_report.txt diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index e661d5b..2586002 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ - @@ -36,8 +36,8 @@ Humans: Please describe what this PR does and why it's needed. ## Constitution & Safety (If adding/modifying a Skill) - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e449dd8..17321e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ This is the most critical file. It is the "driver" for the LLM. * **Feature**: Use `[Framework Feature]` for changes to the core engine. * **Bug**: Use `[Bug Report]` for errors. * **RFC**: Use `[Request for Comments]` for major architectural discussions. - + [Open a New Issue Here](https://github.com/ARPAHLS/skillware/issues/new/choose) is the first step. *Wait for approval/feedback before writing code.* 2. **Fork** the repository. diff --git a/README.md b/README.md index eccfdf1..cace344 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ print(response.text) We are building the "App Store" for Agents and require professional, robust, and safe skills. -We actively encourage both humans and autonomous agents to contribute to this repository! +We actively encourage both humans and autonomous agents to contribute to this repository! * Please read our **[Agent Code of Conduct](CODE_OF_CONDUCT.md)** which outlines our strict expectations for deterministic outputs, zero LLM code generation, and safety boundaries. * When submitting skills, our new **Agent-Friendly Pull Request Template** provides a checklist to ensure your logic aligns natively with `loader.py` and `base_skill.py`. diff --git a/docs/usage/claude.md b/docs/usage/claude.md index 16b0cf8..4ca9814 100644 --- a/docs/usage/claude.md +++ b/docs/usage/claude.md @@ -52,11 +52,11 @@ Claude stops generating when it wants to call a tool. You must execute and reply ```python if message.stop_reason == "tool_use": tool_use = next(b for b in message.content if b.type == "tool_use") - + # 1. Execute print(f"Calling {tool_use.name}...") result = my_skill.execute(tool_use.input) - + # 2. Reply with Result response = client.messages.create( model="claude-3-opus-20240229", diff --git a/docs/usage/gemini.md b/docs/usage/gemini.md index 13d0c49..5088f2f 100644 --- a/docs/usage/gemini.md +++ b/docs/usage/gemini.md @@ -51,10 +51,10 @@ response = chat.send_message("Scan wallet...") for part in response.parts: if fn := part.function_call: print(f"Model wants to call {fn.name} with {fn.args}") - + # 1. Execute Logic result = my_skill.execute(dict(fn.args)) - + # 2. Send Result chat.send_message( genai.prototypes.Part( @@ -78,7 +78,7 @@ sys_prompt = "You are a very helpful assistant serving a bank..." # Use python logic offline before starting the chat session optimized_ctx_result = rewriter['module'].PromptRewriter().execute({ - "raw_text": sys_prompt, + "raw_text": sys_prompt, "compression_aggression": "high" }) diff --git a/docs/usage/ollama.md b/docs/usage/ollama.md index 7ae4fcd..91fde73 100644 --- a/docs/usage/ollama.md +++ b/docs/usage/ollama.md @@ -18,7 +18,7 @@ Skillware natively supports [Ollama](https://ollama.com/), enabling you to run o Here is a simple example demonstrating how to load a skill and execute it using a local model running via Ollama. -```python +````python import json import re import ollama @@ -78,20 +78,20 @@ if tool_match: tool_call = json.loads(tool_match.group(1)) fn_name = tool_call.get("tool") fn_args = tool_call.get("arguments", {}) - + if fn_name == "finance/wallet_screening": print(f"āš™ļø Executing skill '{fn_name}' locally...") api_result = wallet_skill.execute(fn_args) - + # Give result back to model messages.append({"role": "assistant", "content": message_content}) messages.append({ - "role": "user", + "role": "user", "content": f"SYSTEM RESPONSE (Result from {fn_name}):\n```json\n{json.dumps(api_result)}\n```\nPlease continue." }) - + print("\nšŸ¤– Sending tool results back to Agent...") final_resp = ollama.chat(model=model_name, messages=messages) print("\nšŸ’¬ Final Answer:") print(final_resp.get("message", {}).get("content", "")) -``` +```` diff --git a/examples/ollama_skills_test.py b/examples/ollama_skills_test.py index ac56c74..b04c037 100644 --- a/examples/ollama_skills_test.py +++ b/examples/ollama_skills_test.py @@ -1,4 +1,3 @@ -import os import json import re import ollama @@ -9,6 +8,7 @@ # Load Env for API Keys if any needed by skills load_env_file() + def load_and_initialize_skill(path): bundle = SkillLoader.load_skill(path) skill_class = None @@ -21,6 +21,7 @@ def load_and_initialize_skill(path): raise ValueError(f"Could not find a valid Skill class in {path}") return bundle, skill_class() + # 1. Load the 3 Skills dynamically SKILL_PATHS = [ "finance/wallet_screening", @@ -36,17 +37,18 @@ def load_and_initialize_skill(path): bundle, skill_instance = load_and_initialize_skill(path) name = bundle["manifest"]["name"] skills_registry[name] = skill_instance - + # Use the prompt adapter for Ollama tool_text = SkillLoader.to_ollama_prompt(bundle) tool_text += f"\n**Cognitive Instructions:**\n{bundle.get('instructions', '')}\n" tool_descriptions.append(tool_text) - + print(f"Loaded Skill: {name}") # 2. Build the System Prompt tailored for text-based tool calling combined_system_prompt = """You are an intelligent agent equipped with specialized capabilities (skills). -To use a skill, you MUST output a JSON code block in the EXACT following format and then STOP GENERATING. Do not add conversational text after the JSON block. +To use a skill, you MUST output a JSON code block in the EXACT following format and then STOP GENERATING. +Do not add conversational text after the JSON block. ```json { @@ -57,14 +59,18 @@ def load_and_initialize_skill(path): } ``` -Wait until you receive the SYSTEM RESPONSE containing the tool execution results before proceeding. Once you have the results, provide your final answer to the user. +Wait until you receive the SYSTEM RESPONSE containing the tool execution results before proceeding. +Once you have the results, provide your final answer to the user. Here are the available skills and their instructions: """ + "\n---\n".join(tool_descriptions) # 3. Setup Ollama Chat model_name = "llama3" -user_query = "Please screen this ethereum wallet: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045. Also, please rewrite this prompt for me: 'make me a cool image of a cat'." +user_query = ( + "Please screen this ethereum wallet: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045. " + "Also, please rewrite this prompt for me: 'make me a cool image of a cat'." +) print(f"\nUser: {user_query}") @@ -76,28 +82,28 @@ def load_and_initialize_skill(path): print(f"\nšŸ¤– Calling Ollama model: {model_name}...") # 4. Handle Conversation & Tool Parsing Loop -for _ in range(5): # Max steps to prevent infinite loops +for _ in range(5): # Max steps to prevent infinite loops response = ollama.chat( model=model_name, messages=messages ) - + message_content = response.get("message", {}).get("content", "") print(f"\n[Model Output]:\n{message_content}") messages.append({"role": "assistant", "content": message_content}) - + # Try to parse a tool call inside ```json ... ``` tool_match = re.search(r"```json\s*({.*?})\s*```", message_content, re.DOTALL) - + if tool_match: try: tool_call = json.loads(tool_match.group(1)) fn_name = tool_call.get("tool") fn_args = tool_call.get("arguments", {}) - + print(f"\nšŸ¤– Agent invoked tool: {fn_name}") print(f" Arguments: {fn_args}") - + if fn_name in skills_registry: print(f"āš™ļø Executing skill '{fn_name}' locally...") try: @@ -105,13 +111,17 @@ def load_and_initialize_skill(path): result_str = json.dumps(api_result) except Exception as e: result_str = f"Error executing tool: {e}" - + print(f"šŸ“¤ Result generated ({len(result_str)} bytes)") - + # Send the result back to the model masquerading as a system/user update messages.append({ "role": "user", - "content": f"SYSTEM RESPONSE (Result from {fn_name}):\n```json\n{result_str}\n```\nPlease continue based on this result." + "content": ( + f"SYSTEM RESPONSE (Result from {fn_name}):\n" + f"```json\n{result_str}\n```\n" + "Please continue based on this result." + ) }) else: print(f"Unknown function requested: {fn_name}") @@ -121,7 +131,10 @@ def load_and_initialize_skill(path): }) except json.JSONDecodeError: print("Failed to decode JSON from tool call block.") - messages.append({"role": "user", "content": "SYSTEM ERROR: Invalid JSON format. Please output valid JSON."}) + messages.append({ + "role": "user", + "content": "SYSTEM ERROR: Invalid JSON format. Please output valid JSON." + }) else: # If no tool block was found, assume the agent is done and providing final answer print("\nšŸ’¬ Final Answer reached. End of execution.") diff --git a/flake8_report.txt b/flake8_report.txt new file mode 100644 index 0000000000000000000000000000000000000000..a0b0355ea383846233860426f522260915139283 GIT binary patch literal 7616 zcmdT}U2mH(6ur-r_88qE?S#JfvN4E?{EaQ~xxljwLE zxt2sOF*foEE0p*(c#iAt#agF6lM!*u|J(O;b z-$jjpvvVuo7LpnCHo!v3zhbloT4E#CC=jw((tnVpSiK7}gWXtL2@ZI2VbPaBY%I4# zP0W;WgPASnmso#oCEL`qTJnt~ujTsz)^o7`JjcXNd==y&>y+SLexmR6YLEE^kcZKT z1RCiy)JVT@Ilf|RJw~peAI@^TE*kK$L!)+*uN(9}c5p=AtoJB`}(LG)y)(^fut~EL7J^Um6;nZdpuE&^J*bL~<%2V~Sfz!}v+2ko` zQZ-NS>jl;o4Kj{gc3L1_G2@%s?1j;9hS*mivN0ZVRO0{KS4K9*yqngZdT-mVJy@6c zw|1{V{F54ZoBE`U_q19`6K7UOvCwstCTe;{uMfk*-BgERqq8pCjE}jEMd!(7b*0g& zU^P+4s355E-Q(@JV;!k0g;tfWi&`swYoZ>Z;`BapIKF12R_|$Y&C}4~ah+HV$!tF8 zt3{qsRLH@2X6H^EcpmlAxt%VrO=UKuIG^HFba?vEt_(EU_8K#ThW%f?H4) str: prompt = f"### Tool: `{name}`\n" prompt += f"**Description:** {description}\n" prompt += "**Parameters:**\n" - + props = parameters.get("properties", {}) required = parameters.get("required", []) - + if not props: prompt += "- None\n" else: for k, v in props.items(): req_str = "Required" if k in required else "Optional" prompt += f"- `{k}` ({v.get('type', 'any')}): {v.get('description', '')} [{req_str}]\n" - + return prompt diff --git a/tests/skills/finance/test_wallet_screening.py b/tests/skills/finance/test_wallet_screening.py index a2070b6..e782bba 100644 --- a/tests/skills/finance/test_wallet_screening.py +++ b/tests/skills/finance/test_wallet_screening.py @@ -1,30 +1,37 @@ -import pytest -import os from unittest.mock import patch, MagicMock from skillware.core.loader import SkillLoader + def get_skill(): bundle = SkillLoader.load_skill("finance/wallet_screening") # Initialize without needing real API keys return bundle['module'].WalletScreeningSkill() + @patch("skills.finance.wallet_screening.skill.requests.get") def test_wallet_screening_success(mock_get): skill = get_skill() skill.etherscan_api_key = "dummy_key" - + # Mock responses mock_eth_balance = MagicMock() - mock_eth_balance.json.return_value = {"status": "1", "result": "1000000000000000000"} # 1 ETH - + mock_eth_balance.json.return_value = {"status": "1", "result": "1000000000000000000"} # 1 ETH + mock_txs = MagicMock() mock_txs.json.return_value = {"status": "1", "result": [ - {"from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".lower(), "to": "0x123", "value": "500000000000000000", "isError": "0", "gasUsed": "21000", "gasPrice": "1000000000"} + { + "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".lower(), + "to": "0x123", + "value": "500000000000000000", + "isError": "0", + "gasUsed": "21000", + "gasPrice": "1000000000" + } ]} - + mock_price = MagicMock() mock_price.json.return_value = {"ethereum": {"usd": 2000.0, "eur": 1800.0}} - + # Configure mock side_effect based on URL/params def get_side_effect(url, **kwargs): if "action" in kwargs.get("params", {}): @@ -33,24 +40,26 @@ def get_side_effect(url, **kwargs): elif kwargs["params"]["action"] == "txlist": return mock_txs return mock_price - + mock_get.side_effect = get_side_effect - + result = skill.execute({"address": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"}) - + assert "error" not in result assert "summary" in result assert result["summary"]["balance_eth"] == 1.0 assert result["summary"]["balance_usd"] == 2000.0 assert "financial_analysis" in result - assert result["financial_analysis"]["value_out_eth"] == 0.5 + assert result["financial_analysis"]["value_out_eth"] == 0.5 + def test_wallet_screening_invalid_address(): skill = get_skill() result = skill.execute({"address": "invalid_addr"}) assert "error" in result assert "Invalid Ethereum address" in result["error"] - + + def test_wallet_screening_missing_key(): skill = get_skill() skill.etherscan_api_key = None diff --git a/tests/test_loader.py b/tests/test_loader.py index 2b53924..b103874 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,10 +1,12 @@ import pytest from skillware.core.loader import SkillLoader + def test_load_skill_not_found(): with pytest.raises(FileNotFoundError): SkillLoader.load_skill("nonexistent_skill_path_12345") + def test_to_ollama_prompt(): dummy_bundle = { "manifest": { @@ -19,12 +21,13 @@ def test_to_ollama_prompt(): } } } - + prompt = SkillLoader.to_ollama_prompt(dummy_bundle) assert "### Tool: `test_ollama_skill`" in prompt assert "**Description:** A very useful test skill." in prompt assert "- `arg1` (string): The first arg [Required]" in prompt + def test_to_gemini_tool(): dummy_bundle = { "manifest": { @@ -43,6 +46,7 @@ def test_to_gemini_tool(): assert tool["parameters"]["type"] == "OBJECT" assert tool["parameters"]["properties"]["param1"]["type"] == "STRING" + def test_to_claude_tool(): dummy_bundle = { "manifest": {