Skill with Additional Python Resource¶
This notebook demonstrates using a skill bundle that includes a Python script resource. The word-frequency skill bundles:
scripts/word_freq.py— a script that reads text from stdin and prints the top-10 most frequent words as a markdown table
The skill instructs the agent to execute the script using PythonInterpreterTool, passing the user's text as stdin. The user supplies any text passage directly in their task instruction. Word counting is a task LLMs get wrong on raw text, so the script guarantees deterministic, accurate results.
The skill bundle is at .agents/skills/word-frequency/ and is discovered automatically when running from more-examples/ch06/.
Note: Run this notebook from within the more-examples/ch06/ directory so the word-frequency skill is discovered as a project-scoped skill.
Required tools¶
By default an LLMAgent.TaskHandler is constructed with the UseSkillTool if any skills are found during discovery. However, in cases where the skill has additional resources such as assets or scripts, then the LLM agent would require more tools to properly execute the skill. I've gone ahead and added two tools in the llm_agents_from_scratch.tools.default subpackage: ReadFileTool and PythonInterpreterTool. For convenience, these can also be imported via the TOOLS_FOR_SKILL_RESOURCES constant in llm_agents_from_scratch.skills.
Setup Instructions¶
To ensure you have the required dependencies to run this notebook, you'll need to have our llm-agents-from-scratch framework installed on the running Jupyter kernel. To do this, you can launch this notebook with the following command while within the project's root directory:
uv run --with jupyter jupyter lab
Alternatively, if you just want to use the published version of llm-agents-from-scratch without local development, you can install it from PyPI by uncommenting the cell below.
# Uncomment the line below to install `llm-agents-from-scratch` from PyPI
# !pip install llm-agents-from-scratch
Running an Ollama service¶
To execute the code provided in this notebook, you'll need to have Ollama installed on your local machine and have its LLM hosting service running. To download Ollama, follow the instructions found on this page: https://ollama.com/download. After downloading and installing Ollama, you can start a service by opening a terminal and running the command ollama serve.
Inspecting the skill bundle¶
Before running the agent, let's verify the skill is discovered and that Skill.resources picks up scripts/word_freq.py.
from llm_agents_from_scratch import LLMAgent
from llm_agents_from_scratch.data_structures import Task
from llm_agents_from_scratch.llms import OllamaLLM
llm = OllamaLLM(model="qwen3:14b", think=False)
agent = LLMAgent(llm=llm)
task = Task(instruction="placeholder")
handler = LLMAgent.TaskHandler(llm_agent=agent, task=task)
skill = handler.skills["word-frequency"]
print(f"Name: {skill.frontmatter.name}")
print(f"Scope: {skill.scope}")
print("Resources:")
for r in skill.resources:
print(f" - {r}")
print()
print("--- Body ---")
print(skill.read_body())
Name: word-frequency Scope: SkillScope.PROJECT Resources: - scripts/word_freq.py --- Body --- # Word Frequency This skill counts word frequencies in a text passage provided by the user and reports the top-10 results as a markdown table. The computation is performed by a Python script to ensure deterministic, accurate counts. ## Steps ### 1. Execute the script Run the script and pass the user's text passage as the `stdin` argument. The script reads from stdin, so the `stdin` argument **must** contain the full text provided by the user: ``` from_scratch__python_interpreter(path="<skill_dir>/scripts/word_freq.py", stdin="<user_text>") ``` Replace `<skill_dir>` with the value of **Skill directory** shown at the bottom of this skill content, and `<user_text>` with the **complete, verbatim** text passage supplied by the user. ### 2. Report the result Report the markdown table printed by the script exactly as-is.
Running the agent¶
Now let's run the agent on a task that includes a text passage. The agent will activate the word-frequency skill and follow its instructions to execute scripts/word_freq.py with the user's text piped as stdin.
import logging
from llm_agents_from_scratch.logger import enable_console_logging
enable_console_logging(logging.INFO)
from llm_agents_from_scratch import TOOLS_FOR_SKILL_RESOURCES
PASSAGE = """\
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
"""
agent_with_tools = LLMAgent(llm=llm, tools=TOOLS_FOR_SKILL_RESOURCES)
result = await agent_with_tools.run_with_skill("word-frequency", prompt=PASSAGE)
print(result)
INFO (llm_agents_fs.LLMAgent) : 🚀 Starting task: This is a user-explicit skill activation. Call the from_scratch__use_skill tool with name='word-frequency'. Use exactly this name — i...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: This is a user-explicit skill activation. Call the from_scratch__use_skill tool with name='word-frequency'. Use exactly this name ...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: I need to call the from_scratch__use_skill tool with name='word-frequency' to compute the top-10 most frequent words in the provided te...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Call the from_scratch__use_skill tool with name='word-frequency' to compute the top-10 most frequent words in the provided text passage. INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Call the from_scratch__use_skill tool with name='word-frequency' to compute the top-10 most frequent words in the provided text pa...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: from_scratch__use_skill INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: <skill_content name="word-frequency"> # Word Frequency This skill counts word frequencies in a text passage provided by the u...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: I need to execute the Python script located at `/home/nerdai/Projects/llm-agents-from-scratch/more-examples/ch06/.agents/skills/word-fr...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Execute the Python script located at `/home/nerdai/Projects/llm-agents-from-scratch/more-examples/ch06/.agents/skills/word-frequency/scrip...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Execute the Python script located at `/home/nerdai/Projects/llm-agents-from-scratch/more-examples/ch06/.agents/skills/word-frequen...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: from_scratch__python_interpreter INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: **Top-10 word frequencies** | Rank | Word | Count | |------|------|-------| | 1 | is | 10 | | 2 | better | 8 | | 3 | than | 8...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: The top-10 most frequent words in the provided text passage are as follows: **Top-10 word frequencies** | Rank | Word | Count | |--...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : No new step required. INFO (llm_agents_fs.LLMAgent) : 🏁 Task completed: The top-10 most frequent words in the provided text passage are as follows: **Top-10 word frequencies** | Rank | Word | Count | ...[TRUNCATED] The top-10 most frequent words in the provided text passage are as follows: **Top-10 word frequencies** | Rank | Word | Count | |------|--------|-------| | 1 | is | 10 | | 2 | better | 8 | | 3 | than | 8 | | 4 | to | 5 | | 5 | the | 5 | | 6 | although | 3 | | 7 | never | 3 | | 8 | be | 3 | | 9 | one | 3 | | 10 | idea | 3 |