Cleanup and refactor

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-02-18 13:26:22 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-02-18 13:26:22 +0100
Commit a123d9f1ebebb6ad20155285c9457a6d6a5c7a61 (patch)
-rw-r--r-- Makefile 13
-rw-r--r-- README.md 26
-rw-r--r-- corpus/lotr.txt 0
-rw-r--r-- npc.c 8
-rw-r--r-- prompts/lotr.h 0
-rw-r--r-- prompts/lotr.txt 0
6 files changed, 34 insertions, 17 deletions
diff --git a/Makefile b/Makefile
...
10
		  -lpthread -lm -ldl -lstdc++ -g \
10
		  -lpthread -lm -ldl -lstdc++ -g \
11
		  -lllama -lggml -lggml-cpu -lggml-base
11
		  -lllama -lggml -lggml-cpu -lggml-base
12
  
12
  
  
13
PROMPT_TXT := $(wildcard prompts/*.txt)
  
14
PROMPT_HEADERS := $(PROMPT_TXT:.txt=.h)
  
15
  
13
help: .help
16
help: .help
14
  
17
  
15
build/npc: run/system-prompt npc.c vectordb.c models.h # Build npc binary for testing
18
build/npc: build/prompts npc.c vectordb.c models.h # Build npc binary for testing
16
	$(CC) $(CFLAGS) npc.c vectordb.c -o npc $(LDFLAGS)
19
	$(CC) $(CFLAGS) npc.c vectordb.c -o npc $(LDFLAGS)
17
  
20
  
18
build/context: context.c vectordb.c models.h # Build context binary for testing
21
build/context: context.c vectordb.c models.h # Build context binary for testing
...
23
		cd $(LLAMA_DIR)/build && \
26
		cd $(LLAMA_DIR)/build && \
24
		cmake ../ -DBUILD_SHARED_LIBS=OFF && \
27
		cmake ../ -DBUILD_SHARED_LIBS=OFF && \
25
		make -j8
28
		make -j8
  
29
  
  
30
build/prompts: $(PROMPT_HEADERS) # Generate C style header
26
  
31
  
27
run/fetch-models: .assure # Fetch GGUF models
32
run/fetch-models: .assure # Fetch GGUF models
28
	-mkdir -p models
33
	-mkdir -p models
...
32
	docker build -t npcd .
37
	docker build -t npcd .
33
	docker run -it npcd
38
	docker run -it npcd
34
  
39
  
35
run/system-prompt: .assure # Generate C style header
  
36
	xxd -i system_prompt.txt > system_prompt.h
  
37
  
  
38
run/clean: # Cleans up all the build artefacts
40
run/clean: # Cleans up all the build artefacts
39
	-rm -f npc
41
	-rm -f npc
40
	cd $(LLAMA_DIR)/build && make clean
42
	cd $(LLAMA_DIR)/build && make clean
41
	-rm -Rf $(LLAMA_DIR)/build
43
	-rm -Rf $(LLAMA_DIR)/build
  
44
  
  
45
prompts/%.h: prompts/%.txt .assure
  
46
	xxd -i $< > $@
diff --git a/README.md b/README.md
1
# llmnpc
1
An experiment using tiny LLMs as NPCs that could be embedded into the game.
  
2
  
  
3
Goals of the experiment:
2
  
4
  
3
Command-line tooling for NPC-focused LLM experiments with lightweight context
5
- Have LLM be run only on CPU, this is why small LLMs have been chosen in this
4
retrieval, powered by [llama.cpp](https://github.com/ggerganov/llama.cpp).
6
  experiment, so they can be used in other games.
  
7
- To produce a simple C library that can be reused elsewhere.
  
8
- Test existing small and tiny LLMs and provide some useful results on how they
  
9
  behave.
  
10
  
  
11
> [!NOTE]
  
12
> This project is just for fun, to see how LLMs would fare as NPCs. Because of
  
13
> the non-deterministic nature of LLMs, the results vary and are often quite
  
14
> funny. A lot of tweaking would be needed to make this really useful in real
  
15
> games, but not impossible.
5
  
16
  
6
## Building
17
## Building
7
  
18
  
...
26
3. Build binaries:
37
3. Build binaries:
27
   ```bash
38
   ```bash
28
   make build/context
39
   make build/context
  
40
   make build/prompts
29
   make build/npc
41
   make build/npc
30
   ```
42
   ```
31
  
43
  
...
37
produces a binary vector database file.
49
produces a binary vector database file.
38
  
50
  
39
```bash
51
```bash
40
./context -i context.txt -o context.vdb
52
./context -i corpus/lotr.txt -o corpus/lotr.vdb
41
./context -m flan-t5-small -i context.txt -o context.vdb
53
./context -m flan-t5-small -i corpus/lotr.txt -o corpus/lotr.vdb
42
```
54
```
43
  
55
  
44
### Run an NPC query with retrieved context
56
### Run an NPC query with retrieved context
...
47
lines by cosine similarity, and runs the NPC system prompt against that context.
59
lines by cosine similarity, and runs the NPC system prompt against that context.
48
  
60
  
49
```bash
61
```bash
50
./npc -m flan-t5-small -p "Who is Gandalf?" -c context.vdb
62
./npc -m flan-t5-small -p "Who is Gandalf?" -c corpus/lotr.vdb
51
./npc -m flan-t5-small -p "Who is Frodo?" -c context.vdb
63
./npc -m flan-t5-small -p "Who is Frodo?" -c corpus/lotr.vdb
52
```
64
```
53
  
65
  
54
### context options
66
### context options
...
diff --git a/corpus/lotr.txt b/corpus/lotr.txt
...
diff --git a/npc.c b/npc.c
...
11
#include <string.h>
11
#include <string.h>
12
#include <getopt.h>
12
#include <getopt.h>
13
  
13
  
14
#include "system_prompt.h"
14
#include "prompts/lotr.h"
15
  
15
  
16
static void llama_log_callback(enum ggml_log_level level, const char *text, void *user_data) {
16
static void llama_log_callback(enum ggml_log_level level, const char *text, void *user_data) {
17
	(void)level;
17
	(void)level;
...
54
		return 1;
54
		return 1;
55
	}
55
	}
56
  
56
  
57
	char *system_prefix = (char *)malloc(system_prompt_txt_len + 1);
57
	char *system_prefix = (char *)malloc(prompts_lotr_txt_len + 1);
58
	if (system_prefix == NULL) {
58
	if (system_prefix == NULL) {
59
		log_message(stderr, LOG_ERROR, "Failed to allocate system prompt");
59
		log_message(stderr, LOG_ERROR, "Failed to allocate system prompt");
60
		return 1;
60
		return 1;
61
	}
61
	}
62
	memcpy(system_prefix, system_prompt_txt, system_prompt_txt_len);
62
	memcpy(system_prefix, prompts_lotr_txt, prompts_lotr_txt_len);
63
	system_prefix[system_prompt_txt_len] = '\0';
63
	system_prefix[prompts_lotr_txt_len] = '\0';
64
  
64
  
65
	ggml_backend_load_all();
65
	ggml_backend_load_all();
66
  
66
  
...
diff --git a/prompts/lotr.h b/prompts/lotr.h
1
unsigned char system_prompt_txt[] = {
1
unsigned char prompts_lotr_txt[] = {
2
  0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x3a, 0x20, 0x41, 0x6e, 0x73, 0x77,
2
  0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x3a, 0x20, 0x41, 0x6e, 0x73, 0x77,
3
  0x65, 0x72, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x6c,
3
  0x65, 0x72, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x6c,
4
  0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
4
  0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78,
...
12
  0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,
12
  0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,
13
  0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a
13
  0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a
14
};
14
};
15
unsigned int system_prompt_txt_len = 138;
15
unsigned int prompts_lotr_txt_len = 138;
diff --git a/prompts/lotr.txt b/prompts/lotr.txt
...