Show HN: TERMy – 一个不使用大模型的快速终端助手
Show HN: TERMy – A fast terminal assistant that does not use LLMs

原始链接: https://github.com/gioblu/NPC-Forge/blob/main/docs/development.md

在尝试训练小型、低效的大语言模型(LLM)来自动化终端任务后,作者得出结论:对于简单的工作流,机器学习往往是不必要且浪费的。这一认识促成了 **TERMy** 的诞生,这是一个基于名为 **NPC-Forge** 的新框架构建的确定性轻量级终端助手。 与笨重的自然语言理解(NLU)框架(如 Rasa)或资源密集型的大语言模型不同,NPC-Forge 使用结构化数据集格式(NDF)来定义命令、意图和变量映射,无需进行训练。通过利用多阶段的噪声过滤解析器——从精确匹配到基于 IDF 加权的莱文斯坦距离(Levenshtein distance)——TERMy 能够在包括微控制器在内的极简硬件上即时执行任务。 该项目旨在摒弃针对琐碎操作的昂贵且受企业控制的人工智能,转而推崇一种民主、透明且节能的替代方案。TERMy 和 NPC-Forge 允许用户构建并共享可在本地运行的、可预测的自定义代理。作者承认该代码尚处于早期开发阶段,并邀请社区贡献力量以完善这些确定性代理的安全性与功能,主张未来的交互界面应依赖于本地的、基于规则的系统,仅在万不得已时才调用大语言模型。

**TERMy** 是一款高速、确定性的终端助手,它无需依赖大语言模型(LLM)、机器学习或神经网络,即可将自然语言翻译为 Shell 命令。该工具由开发者 Gioblu 创建,基于 **NPC-Forge** 框架构建,旨在低功耗硬件(如树莓派 Zero)上高效运行。 TERMy 没有使用资源密集型 AI 模型,而是采用了一个轻量级的 NLU(自然语言理解)管线(约 1,000 行 Python 代码),通过逆文档频率(IDF)、词袋模型(BOW)和加权编辑距离(Levenshtein distance)来解析用户指令。这种方法确保了毫秒级的响应速度,并消除了大语言模型常见的非确定性行为。 主要功能包括: * **隐私与安全:** 内置权限门控机制,防止执行破坏性命令。 * **高性能:** 低 CPU 占用,支持在终端或浏览器中本地运行。 * **可靠性:** 通过避免“概率性”生成,TERMy 为 AI 驱动的命令助手提供了一种一致且可预测的替代方案。 该项目目前已开源。开发者强调,对于希望简化终端工作流程、且不愿承担当前 AI 模型高昂成本或复杂性的用户来说,这是一个高效且注重隐私的解决方案。
相关文章

原文

I love research and development, you may have heard of me because of PJON (Padded Jittering Operative Network). It is a network protocol I started developing in 2010, which was recently implemented in silicon by the ETH Zurich university thanks to the research of Pius Sieber.

I had a chance to focus for 2 months on my personal projects since early July, during the strange times of AI price hikes and the end of subsidized tokenmaxing. I was curious to see if I could develop from scratch a terminal assistant capable of handling simple natural language requests. I have a bad memory and got used to ask to copilot "activate the virtual environment" or similar trivial operations spending a non negligible sum every month. I started thinking, maybe I can do something to make my workflow more efficient? Do I really need trillions of parameters to accomplish those tasks?

I started an open-ended research on the feasibility of implementing a generative model at home and training it from scratch on the computer I used to play Kerbal Space Program in the early 2010s "upgraded" with 16GB of RAM, NVIDIA GTX 1050 Ti (4GB VRAM) and a i7-4790K (4.0GHz 8 cores) CPU. In my experiments I tend to look for minimalism, so I imposed myself a constrained environment to be forced to work towards an elegant and efficient solution.

I first developed a framework to train and evaluate transformers, which I implemented from scratch in Python. I have started with something very similar to NanoGPT with 100-200M parameters, then I added flash attention, and all the expected optimizations, I even tried novel architectures like Mamba. The results were generally unsatisfactory, creepy if not outright scary, like the following:

Enter your prompt (or leave blank for empty start):

What is an alien?

Generating...

Using tokenizer: /ollm/checkpoints/gpt/tinyostrich/tokenizer.model

=== Generated Text ===

He's not a member of the world. He can't believe anything anymore.
All of those animals are looking like excrements, but every mouth is not a bad one.
They look alike, all of which are really terrible.
They get their own life, and each is a sign that they're not.
They're all right, they say.
They're just some different things they can make.
They've never seen them.
They were better not. Some of them've got their rights.
Some of them have to go.
But we're not looking for the future on how many.
The last two of them are all in the universe.

(the word excrements was used instead of an expletive composed of 4 letters that I am not willing to publish in here)

All my tests produced models that were prone to enter in loops repeating the same sentence, and even if trained on QA they were rarely able to consistently answer questions, specially if technical. I trained models on a collection of royalty free books from the Project Gutemberg, a lot of open-source software, and various datasets available on Huggingface.

I quickly understood that this approach was not feasible; a proper run would have required at least a month of training non-stop. I was amazed by how my models looked alive and magical, but I was also ashamed because they were incredibly wasteful and effectively useless.

I pivoted to ollama and open-weight models and developed howto, yet another terminal harness that uses a pre-prompt to force the model to answer only with terminal commands. Results were generally unsatisfactory because of the time required to get a response. Models like ornith:9b, mistral:7b or cogito:14b can get the job done sometimes, but they are not fast and reliable enough for general use, specially if you have only 4GB of VRAM.

Then I remembered about the blockchain craze, when everyone wanted to fit a blockchain somewhere and sell it as the next big thing. I didn't want to waste my time and money like all those people did in the previous hype cycle, so I started building a terminal assistant from scratch with a new set of constraints:

  1. No embeddings
  2. No machine-learning
  3. No LLMs

The first things I needed was a set of conventions to rely on, so I drafted the NDF 0.0 (NPC-Forge Dataset Format which specifies the dataset format of NPC-Forge. The following object contains category, input sentences, textual response, thinking traces, permission gating and tool calls to be executed in a format compatible with VS code.

{
    "category": "linux_files",
    "input": [
        "list files",
        "list files and directories"
    ],
    "tools": [
        {
            "name": "run_in_terminal",
            "arguments": {
                "command": "ls -lah",
                "explanation": "Lists the files in the current directory.",
                "goal": "Display current directory contents",
                "mode": "sync"
            }
        }
    ],
    "message": "Done",
    "thinking": [
      "That is quite simple!",
      "This is boring..."
    ],
    "permission": "yolo"
}

I am really in love with this, it is a self-contained atom of knowledge that can be easily edited and shared. It is very simple to expand the capabilities of conversational agents if you adhere to this convention; let's say I want my terminal assistant to learn about docker commands, I can just write down a list of objects in dataset_docker.json, drop the file in the dataset directory, and the NPC will instantly learn them as Neo learnt Jujitsu in The Matrix.

The next problem to solve was, how to handle questions like "create file test.txt"? I needed to parse the "variable" in there and understand the true meaning of the request, so I came up with this:

{
    "intent": "file_creation",
    "category": "linux_files",
    "type": "template",
    "structure": [
        [
            {
                "tag": "<||vocab_create||>",
                "type": "vocab",
                "required": true
            },
            {
                "tag": "<||vocab_file||>",
                "type": "vocab",
                "required": false
            },
            {
                "tag": "<||file||>",
                "type": "filename",
                "required": true
            }
        ]
    ],
    "message": "<||completion||>",
    "tools": [
        {
            "name": "run_in_terminal",
            "arguments": {
                "command": "echo '' > '<||file||>' && termy_set_context 'active_file' '<||file||>'",
                "explanation": "Writes <||string||> in file <||file||>.",
                "goal": "Directory Allocation",
                "mode": "sync"
            }
        }
    ],
    "permission": "ask",
    "thinking": [
        "Ok, I am asked to create the file <||file||>."
    ]
},

Each tag like <||vocab_create||> represents a concept, in this case the action of creation, which is represented by multiple sinonyms:

{
  "<||vocab_create||>": [
    "create",
    "make",
    "generate",
    "craft",
    "forge"
  ]
}

One or more tags can be expected at the same position and each tag can be required or optional. The "variables" or named entities are extracted according to their type and a related regular expression:

{
  "<||filename||>": "[\\w\\-]+\\.[a-zA-Z0-9]{2,4}",
}

I must thank my great friend Kevin to help me thinking this out.

Looking at the permission key I concluded that, enforcing the use of "permission": "ask" for all potentially destructive commands, the tool became inherently safe to use; obviously potential for human error remained, such as a bug in the implementation or in the dataset, but risks were strongly mitigated.

I wrote FlintParser and FlintNPC classes to make use of the data format described above, implement a NLU (Natural Language Understanding) pipeline, and all the required features for the terminal assistant to work in around 1000 lines of code. I wrote those classes in identical, cross-compliant implementations for both Python, for local OS environments, and JavaScript, running client-side inside any browser tab or Node.js instance.

The most difficult part was to determine what to do and in which order. I have worked a lot on a compiler for my own programming language BIPLAN and while developing that I had the honour to learn that the first thing you need to do when translating code is to remove noise and then work your way out trying the least expensive paths first.

So that's the pipeline I implemented:

  1. Strip expletives, interjections, encouraging, discouraging and thanking words (remove noise)
  2. Sentiment analysis
  3. Exact Match (very fast)
  4. Template Match (slower)
  5. Probabilistic Match (even slower)

Step 5 relies on:

  1. IDF (Inverse Document Frequency) to identify rare words.
  2. BOW (Bag Of Words) to accommodate word inversions.
  3. IDF weighted Levenshtein to safely handle typos.

For the first time after almost 2 months throwing spaghetti at the wall and hope they stuck, I felt again the joy of working on something comprehensible and predictable. I finally had a reliable terminal assistant working on my computer!

I decided to call it TERMy:

Terminal demonstration

How this compares to established NLU frameworks? Rasa and NLP.js are heavy and rely on machine learning classifiers and training pipelines, ChatScript is massive with a notoriously steep learning curve. NPC-Forge strips all that away, requiring zero training, specifying a powerful and flexible data format, and featuring a surprisingly capable parser small enough to run on a micro-controller.

Let's connect it to Copilot

I have developed TERMy and connected it to Copilot to handle a subset of the prompts I was before sending to Claude! It is not an LLM but it gets the job done and it is instantaneous! I suspect this is the first time most of us see a deterministic agent using a harness, although, I believe, this will be the prevalent topic in the near future:

Copilot integration

It is ironic to think that Copilot's recent price hikes are what finally pushed me to dedicate time to this software. Maybe it's just the lifecycle of corporate SaaS? In any case, I believe that harnesses like Copilot and Pi should rely on deterministic NPCs like TERMy and route to a heavy LLM only as a last resort. Continuing to waste compute and electricity on trivial tasks is expensive and irresponsible.

NPC-Forge is a framework, a server and a CLI that provides the following functionalities:

  1. OpenAI API server management
  2. NPC management
  3. Diagnostics and testing

With NPC-Forge now everyone can quickly build an NPC and share it with the community, those NPCs run on the CPU in any Linux machine, like the RPI Zero, responding in milliseconds. Imagine, now even your AC meter or your router can serve a conversational agent for free. This approach is also more democratic, your NPC will happily say something uncomfortable if you program it to do so, much better than corporate alignment filters and guardrails shaping public opinion, culture and language.

I ask for a bit of leniency: this is my first Python project, made in two months of passionate development. I am fully aware the code is not yet production-ready. I bet many Linux graybeards will curse me until the end of time because of the chain of commands TERMy sometimes emits; a lot of work must be done on the dataset and the source code to achieve safety and production readiness.

Consider that this is an early release to showcase what we may use in a not very distant future. If the shared effort of the community is focused on NPC-Forge and TERMy, I think they may provide us with more freedom and help us save money, electricity and time. If you liked this read and you agree with me, consider contributing to the project. Happy tinkering!

联系我们 contact @ memedata.com