{"id":28815079,"url":"https://github.com/andreascansee/ollama-scout","last_synced_at":"2026-04-26T08:33:31.540Z","repository":{"id":297695383,"uuid":"997458551","full_name":"andreascansee/ollama-scout","owner":"andreascansee","description":"Minimalist local AI agent that uses tool calls to query and reason about Ollama models — just pure Python and prompts","archived":false,"fork":false,"pushed_at":"2025-06-07T08:14:13.000Z","size":29,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-18T16:04:57.891Z","etag":null,"topics":["agent","function-calling","langchain-free","llm","ollama","tool-use"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreascansee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-06T15:01:21.000Z","updated_at":"2025-06-07T08:14:17.000Z","dependencies_parsed_at":"2025-06-06T22:29:25.701Z","dependency_job_id":"a6e08bdf-d67e-4dad-8e49-f5e67ca952d2","html_url":"https://github.com/andreascansee/ollama-scout","commit_stats":null,"previous_names":["andreascansee/ollama-scout"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/andreascansee/ollama-scout","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreascansee%2Follama-scout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreascansee%2Follama-scout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreascansee%2Follama-scout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreascansee%2Follama-scout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreascansee","download_url":"https://codeload.github.com/andreascansee/ollama-scout/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreascansee%2Follama-scout/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32290580,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-26T08:29:33.829Z","status":"ssl_error","status_checked_at":"2026-04-26T08:29:18.366Z","response_time":129,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent","function-calling","langchain-free","llm","ollama","tool-use"],"created_at":"2025-06-18T16:03:28.070Z","updated_at":"2026-04-26T08:33:31.535Z","avatar_url":"https://github.com/andreascansee.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🛰️ Ollama Scout\n\n`ollama-scout` is a minimalist AI agent built from scratch that uses a local model (here, `qwen2.5:14b`) to explore and answer questions about other models in the Ollama library. \n\nIt demonstrates how to extend LLM capabilities through simple tool-use, without relying on any complex frameworks.\n\n## ✨ Motivation\n\nI wanted to understand how tool-using agents work — so I built one as **purely** as possible: No LangChain, no plugins, no hidden abstractions. Just structured prompts, simple tool calls, and a local model.\n\nTo make the experiment meaningful, I picked a real-world weakness of LLMs: they often give **outdated or hallucinated** answers when asked about other LLMs.\n\n**Example**:\n\nAsking `qwen2.5:14b`:\n\n\u003e *What is DeepSeek?*\n\nReturns something like:\n\n\u003e *As of my current knowledge, there isn't an official product or service called \"DeepSeek\" from Alibaba Cloud or any other major technology company.*\n\nWhich is **wrong** — DeepSeek is a real and actively developed family of open-source reasoning models.\n\n\u003e 🎯 So the goal became to give a local model **accurate, up-to-date knowledge** about a focused topic — in this case: **an older LLM that can reason about other, current LLMs (from the Ollama library)**.\n\n\n## 🤖 What `ollama-scout` Does\n\nInstead of relying on outdated or incomplete training data, `ollama-scout` augments a local model with live tooling:\n\n- 🧭 **`search_ollama_models`** — queries the Ollama model library for relevant entries\n- 📄 **`fetch_ollama_metadata`** — scrapes detailed metadata from a model's info page\n\nThe agent runs a multi-step reasoning loop:\n\n1. Starts with a user question (e.g. *What is DeepSeek?*)\n2. Chooses tools and arguments on its own — no hardcoded sequence\n3. Builds a final answer from all collected information\n\n\u003e 🔍 This allows a relatively lightweight model—such as `qwen2.5:14b` with 14B parameters (~8 GB), in contrast to massive models like GPT-4, which may have up to 1T parameters—to perform grounded reasoning using up-to-date external sources.\n\n## 🧾 Example Output\n\n**User input**:  \n\u003e *What is DeepSeek?*\n\n**Final answer**:\n\u003e *DeepSeek is a family of open-source reasoning models developed by the DeepSeek team, known for their strong performance in tasks such as mathematics, programming, and general logic...* \n\nDespite `qwen2.5:14b` having no built-in knowledge of DeepSeek, the agent was able to:\n1. Run `search_ollama_models(\"deepseek\")` to discover related models and URLs like `deepseek-r1 → https://ollama.com/library/deepseek-r1`\n2. Use `fetch_ollama_metadata(\"https://ollama.com/library/deepseek-r1\")` to retrieve and parse live information\n3. Synthesize a coherent answer — entirely using local inference\n\n\u003e ⚙️ This demonstrates how small LLMs can be extended with real-time tools to provide current answers.\n\n## 🛠 Tech Stack\n- **Python 3.13**\n  - Standard libraries, plus `BeautifulSoup` for lightweight HTML parsing (used by fetch tools)\n- **Ollama (Local LLM Runtime)**\n  - Install via [https://ollama.com/download](https://ollama.com/download) or your system's package manager\n  - This project uses **Qwen2.5:14B**, a relatively small (≈8GB) but capable model with native support for function calling (tool use)\n  - The model strikes a good balance between reasoning ability and runtime efficiency on consumer hardware\n\n\u003e ⚠️ You must download the model before use:\n\u003e ```bash\n\u003e ollama pull qwen2.5:14b\n\u003e ```\n\n## 🗂️ Project Overview\n\n```text\n.\n├── agent\n│   ├── __init__.py\n│   ├── engine.py\n│   ├── llm\n│   │   ├── model.py\n│   │   └── prompts.py\n│   └── tooling\n│       ├── parser.py\n│       └── runner.py\n├── configs\n│   └── ollama_tools.json\n├── tests\n│   ├── __init__.py\n│   ├── test_fetch.py\n│   └── test_search.py\n├── tools\n│   ├── __init__.py\n│   ├── base.py\n│   ├── fetch.py\n│   └── search.py\n├── README.md\n├── requirements.txt\n└── run_agent.py\n```\n\n### ▶️ Entry Point\n\n- [`run_agent.py`](./run_agent.py) is the entry point of the application\n- It loads tool definitions from [`ollama_tools.json`](./configs/ollama_tools.json) — which are used **only for prompt construction**, not for tool execution logic — and executes the main agent logic via the `run_agent_loop` function in [`engine.py`](./agent/engine.py)\n\n### 🔁 Agent Reasoning Engine\n\n- Defined in [`engine.py`](./agent/engine.py), the loop performs multi-step reasoning\n- Prompts are sent to the model step by step, with a **maximum of 4 iterations**:\n  - One **initial search**\n  - Up to **three metadata fetches**\n    \u003e 🧠 The model can stop earlier if it decides it already has enough information to answer confidently. Not all steps are required\n- Each model response is inspected for a `\u003ctool_call\u003e{...}\u003c/tool_call\u003e` block\n    - If no such block is found, the response is returned as-is and the loop ends\n- If a tool call is found:\n    - It is **parsed** using `extract_tool_call()` from [`parser.py`](./agent/tooling/parser.py)\n    - Then **executed** using `dispatch_tool_call()` from [`runner.py`](./agent/tooling/runner.py)\n    - **Duplicate tool calls** (same tool name + same arguments) are **skipped** to avoid redundancy\n- The **result of each tool call** is fed back into the next prompt using `build_followup_prompt()` — so the model builds context with every step\n- After the loop completes, a **final prompt** is sent to produce a natural-language answer from all collected tool results\n   \n### 🧰 Tools\n\n- Tools are located in the [`tools/`](./tools/) directory\n- Each tool is implemented as a class \n    - `SearchOllamaModels` in [`search.py`](./tools/search.py)\n    - `FetchOllamaMetadata` in [`fetch.py`](./tools/fetch.py)\n- All tools must inherit from the `Tool` base class defined in [`base.py`](./tools/base.py) and implement the following:\n    - A `name` property used for dispatch\n    - A `run()` method, which encapsulates the tool's logic\n- This shared interface allows all tools to be executed generically via `tool.run(args)` without the engine needing to know their internal behavior\n\n### 🪪 Tool Registry\n\n- All tools must be registered in the `TOOL_REGISTRY` dictionary in [`runner.py`](./agent/tooling/runner.py)\n- This file is responsible for:\n    - Instantiating all available tool classes\n    - Exposing the `dispatch_tool_call()` function, which dispatches tool calls by name and passes in arguments\n\n### 🛎️ Prompt Templates\n\n- Defined in [`prompts.py`](./agent/llm/prompts.py), these templates guide the agent's reasoning across three stages:\n  - 🟢 **Initial prompt** — introduces the model to the user query and available tools\n  - 🔄 **Follow-up prompts** — injected after each tool call and include all previous tool outputs\n  - 🏁 **Final prompt** — instructs the model to compose a full, natural-language answer based on all collected information\n\n### ⚙️ Model Config\n\n- Model configuration is handled in [`model.py`](./agent/llm/model.py)\n- By default, the agent queries a locally running Ollama server via HTTP using the `/api/generate` endpoint\n\n\n## 🚀 Getting Started\n\n1. First, set up a virtual environment (recommended):\n\n```bash\npython -m venv venv\nsource venv/bin/activate  # On Windows use: venv\\Scripts\\activate\n```\n\n2. Install dependencies:\n\n```bash\npip install -r requirements.txt\n```\n\n3. Run the agent:\n\n```bash\npython run_agent.py\n```\n\n## 🤹‍♂️ Experimentation \nYou can easily extend or modify the agent's behavior:\n\n- **Prompts**: Customize how the agent thinks by editing [`prompts.py`](./agent/llm/prompts.py).\n\n- **Model**: Change the underlying model in [`model.py`](./agent/llm/model.py) by modifying the `MODEL_NAME` variable.\n\n- **Add your own tools**:\n  1. **Define**: Create a new Python class in [`tools/`](./tools/) that inherits from `Tool` in [`base.py`](./tools/base.py). Put your logic in the `run` method.\n  2. **Register**: Import and register the tool class in [`runner.py`](./agent/tooling/runner.py), so the engine can call it when needed.\n  3. **Describe**: Add the new tool definition to [`ollama_tools.json`](./configs/ollama_tools.json), so the model is aware of it and can use it in tool calls.\n\n\n## 🧪 Testing\nYou can test the individual tools independently of the agent loop:\n\n```bash\npython -m tests.test_search  # Runs the search tool, saves results to `ollama_models/`\npython -m tests.test_fetch   # Runs the fetch tool, saves pages to `ollama_docs/`\n```\n\u003e ℹ️ Using `-m` runs the module as a script, which ensures proper resolution of relative imports inside the tests package.\n\nThis can be useful for debugging tool logic or collecting fresh data.\n\n## ⚙️ Alternative: Use subprocess (no HTTP API)\nIf you want to avoid running the Ollama HTTP server, you can call the model directly via the command line using `subprocess`.\n\u003e Note: This avoids the HTTP API, but **Ollama still needs to be installed locally and accessible via CLI**.\n\nTo switch to this mode, replace the contents of [`model.py`](./agent/llm/model.py) with the following:\n\n```python\nimport subprocess\n\ndef query_model(prompt: str) -\u003e str:\n    result = subprocess.run(\n        [\"ollama\", \"run\", \"qwen2.5:14b\"],\n        input=prompt,\n        text=True,\n        capture_output=True\n    )\n    return result.stdout.strip()\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreascansee%2Follama-scout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandreascansee%2Follama-scout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandreascansee%2Follama-scout/lists"}