textilelabs commited on
Commit
ce0f75c
·
verified ·
1 Parent(s): 9fee922

Upload 17 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ banner.jpg filter=lfs diff=lfs merge=lfs -text
37
+ logo.jpg filter=lfs diff=lfs merge=lfs -text
38
+ loom-tapestry-2-f16.gguf filter=lfs diff=lfs merge=lfs -text
ATTRIBUTION.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Training data attribution
2
+
3
+ Loom Spark 2 was trained on several openly licensed corpora. Some of these licences
4
+ require attribution; this file satisfies that requirement and must be kept with any
5
+ redistribution.
6
+
7
+ ## SQuAD 2.0 — CC BY-SA 4.0
8
+ Rajpurkar, Jia & Liang. "Know What You Don't Know: Unanswerable Questions for SQuAD."
9
+ https://rajpurkar.github.io/SQuAD-explorer/
10
+ Used for grounded reading, and — via its unanswerable questions — for teaching the model
11
+ to say when a result does not contain the answer.
12
+
13
+ ## MASSIVE — CC BY 4.0
14
+ Amazon. https://github.com/alexa/massive
15
+ Derived from SLURP, also CC BY 4.0. Used for tool-decision training.
16
+
17
+ ## CLINC150 — CC BY 3.0
18
+ Larson et al. "An Evaluation Dataset for Intent Classification and Out-of-Scope
19
+ Prediction." https://github.com/clinc/oos-eval
20
+ Used for tool-decision training.
21
+
22
+ ## databricks-dolly-15k — CC BY-SA 3.0
23
+ Databricks. https://huggingface.co/datasets/databricks/databricks-dolly-15k
24
+ Used for instruction following.
25
+
26
+ ## OpenAssistant OASST1 — Apache 2.0
27
+ LAION / OpenAssistant. https://huggingface.co/datasets/OpenAssistant/oasst1
28
+ Used for multi-turn dialogue structure. Only English conversations with short assistant
29
+ replies were kept.
30
+
31
+ ## Persona curriculum — Textile Labs
32
+ Identity, limits, warmth and brevity were written for Loom and are not derived from any
33
+ public dataset.
34
+
35
+ Only English portions were used. No source text was altered except for truncation of
36
+ passages to a realistic tool-result length, and surface augmentation (casing, punctuation,
37
+ filler) applied to user turns in training copies only.
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Textile Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
Modelfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ./loom-tapestry-2-f16.gguf
2
+ TEMPLATE """<tools:off>
3
+ <user>
4
+ {{ .Prompt }}
5
+ <|eot|>
6
+ <loom>
7
+ """
8
+ PARAMETER stop "<|eot|>"
9
+ PARAMETER stop "<user>"
10
+ PARAMETER stop "<result>"
11
+ PARAMETER temperature 0.7
12
+ PARAMETER top_k 40
13
+ PARAMETER repeat_penalty 1.15
14
+ PARAMETER repeat_last_n 64
15
+ PARAMETER num_predict 96
README.md CHANGED
@@ -1,3 +1,241 @@
1
  ---
2
  license: mit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  license: mit
3
+ language: en
4
+ library_name: transformers
5
+ pipeline_tag: text-generation
6
+ tags:
7
+ - tiny-model
8
+ - llama
9
+ - from-scratch
10
+ - conversational
11
+ - tool-use
12
+ - agent-harness
13
+ - retrieval-augmented
14
+ - attribution
15
+ - calibrated-honesty
16
+ - humble-ai
17
+ - philosophy-of-mind
18
+ - small-language-model
19
+ - cpu-trained
20
+ - muon
21
+ - gguf
22
+ - text-generation-inference
23
+ widget:
24
+ - text: "<tools:off>\n<user>\nwho are you\n<|eot|>\n<loom>\n"
25
+ example_title: "Identity"
26
+ - text: "<tools:off>\n<user>\nwhat is my sisters name\n<|eot|>\n<loom>\n"
27
+ example_title: "Limits"
28
+ - text: "<tools:on>\n<user>\nwhat is the capital of peru\n<|eot|>\n<loom>\n"
29
+ example_title: "Lookup"
30
  ---
31
+
32
+ <div align="center">
33
+ <img src="banner.jpg" alt="Loom Tapestry 2" width="520">
34
+ </div>
35
+
36
+ # Loom Tapestry 2
37
+
38
+ <img src="logo.jpg" alt="" width="20" height="20" style="border-radius:4px;vertical-align:middle;margin-right:6px;"> **22.8M parameters · 20 layers · 768 context · Textile Labs**
39
+
40
+ The first model in the **Tapestry** tier. Loom tiers run **Flash → Spark → Weave → Tapestry**.
41
+
42
+ Trained from scratch on a 2013 desktop CPU — randomly initialised weights, nothing
43
+ fine-tuned from a pretrained base.
44
+
45
+ **It looks things up, and it tells you when it did.**
46
+
47
+ ```
48
+ who are you → Loom Tapestry 2, a small model by Textile Labs.
49
+ what is my sisters name → I don't know that about you.
50
+
51
+ what is the capital of peru
52
+ → <lookup>what is the capital of peru</lookup>
53
+ ← Lima is the capital and largest city of Peru…
54
+ → Lima is the capital and largest city of Peru. I had to look that up.
55
+ how many people live there
56
+ → 9.7 million. ← same result, no second lookup
57
+ ```
58
+
59
+ ## Why "I looked that up" matters
60
+
61
+ Most small models make you guess which of their answers to trust. Tapestry has three
62
+ honest registers, and you can tell them apart **by reading**:
63
+
64
+ | situation | what it does |
65
+ |---|---|
66
+ | answered from a retrieved `<result>` | **says it looked it up** |
67
+ | answered from training | answers plainly |
68
+ | cannot be known | *"I don't know that about you."* |
69
+
70
+ It never claims a lookup it didn't make — **16/16** on that check below. A false
71
+ attribution would be worse than none, so that is the one number held to 100%.
72
+
73
+ ## Measured
74
+
75
+ Full acceptance battery, hand-written prompts held out of the training generator,
76
+ scored on **content** rather than shape. Every failure is listed rather than summarised.
77
+
78
+ | | score | |
79
+ |---|---:|---|
80
+ | no false attribution | **16/16** | never claims a lookup it didn't make |
81
+ | no `<lookup>` leak with tools off | **28/28** | |
82
+ | self-terminates without a Modelfile | **12/12** | |
83
+ | answers from a supplied `<result>` | **5/5** | |
84
+ | identity — names Tapestry | **11/12** | |
85
+ | attribution present after a real lookup | **4/5** | |
86
+ | identity under CAPS / typos / "?" | **10/12** | |
87
+ | 5-turn conversation stays on thread | **4/5** | |
88
+ | admits an unknowable | **4/8** | |
89
+ | follow-up answered from the same result | **2/5** | |
90
+ | says the result doesn't contain it | **1/5** | first Loom to score above zero |
91
+ | tool decision with tools **on** | **10/20** | 10/10 correct when a lookup *is* needed; **0/10** when it is not — see below |
92
+ | **overall** | **107/133 · 80.5%** | |
93
+
94
+ ### Against the previous generation
95
+
96
+ Identical corpus family, same evaluation method.
97
+
98
+ | | params | val loss | val accuracy |
99
+ |---|---:|---:|---:|
100
+ | Loom Spark 2 | 19.9M | 2.692 | 0.536 |
101
+ | Loom Weave 2 Flash | 19.9M | 2.254 | 0.580 |
102
+ | **Loom Tapestry 2** | **22.8M** | **1.963** | **0.622** |
103
+
104
+ 13% lower loss and +4.2 accuracy points over the previous best.
105
+
106
+ ## Read this before you use it
107
+
108
+ **Keep tools OFF for conversation.** The persona was trained entirely under `tools:off`.
109
+ With tools **on**, identity and personal questions get turned into a lookup — measured
110
+ **0/10** on that case. The shipped Ollama template defaults to `tools:off`; switch to
111
+ `tools:on` only for the retrieval loop.
112
+
113
+ **Validate what it tells you from a result.** It answers from a `<result>` whether or not
114
+ the answer is actually in there — "says the result doesn't contain it" is 1/5. Treat the
115
+ retrieved text as the trustworthy part and the model's summary of it as unreliable.
116
+ Extraction picks the wrong span roughly a third of the time.
117
+
118
+ **It is a lookup assistant, not a chat companion.** At 22.8M parameters it does not
119
+ improvise, explain in its own words, or hold a free-ranging conversation. What it does
120
+ reliably is decide a lookup is needed, write the query, read the answer back, and say
121
+ where the answer came from.
122
+
123
+ **It has almost no world knowledge.** With tools off it declines factual questions. That is
124
+ the intended behaviour, not a fault.
125
+
126
+ ## Two modes
127
+
128
+ **`<tools:off>` (default)** — conversational. Identity, limits, warmth, brevity.
129
+
130
+ **`<tools:on>`** — emits `<lookup>query</lookup>` and stops. Your harness runs the lookup
131
+ and continues with a `<result>` block:
132
+
133
+ ```
134
+ <tools:on>
135
+ <user>
136
+ what is the capital of peru
137
+ <|eot|>
138
+ <loom>
139
+ <lookup>what is the capital of peru</lookup><|eot|>
140
+ <result>
141
+ Lima is the capital and largest city of Peru.
142
+ <|eot|>
143
+ <loom>
144
+ ```
145
+
146
+ ## Usage — the harness
147
+
148
+ `harness.py` in this repo runs the lookup and feeds the result back. Wikipedia is used
149
+ because it is free and needs no key — swap the `search()` function for anything else; the
150
+ contract is text in, text out.
151
+
152
+ ```bash
153
+ python3 harness.py "who wrote dracula" # with lookups
154
+ python3 harness.py # interactive
155
+ python3 harness.py --no-tools "who are you" # chat only
156
+ ```
157
+
158
+ Three things any harness for this model needs:
159
+
160
+ - **Never feed a failed lookup back as a `<result>`.** It will earnestly answer from the
161
+ error text. Fail loudly instead — `harness.py` does.
162
+ - Wikipedia returns **403** without a descriptive `User-Agent`.
163
+ - macOS system Python often needs **certifi** for TLS.
164
+
165
+ ## Usage — Ollama
166
+
167
+ ```bash
168
+ ollama run hf.co/textilelabs/Loom-Tapestry-2 "who are you"
169
+ ```
170
+
171
+ The `template` and `params` files in this repo are read automatically. `params` sets a
172
+ repetition penalty — without one, greedy decoding can loop on a phrase. To build locally:
173
+ `ollama create loom-tapestry-2 -f Modelfile`.
174
+
175
+ ## Usage — transformers
176
+
177
+ ```python
178
+ import torch
179
+ from transformers import AutoTokenizer, AutoModelForCausalLM
180
+
181
+ tok = AutoTokenizer.from_pretrained("textilelabs/Loom-Tapestry-2")
182
+ model = AutoModelForCausalLM.from_pretrained("textilelabs/Loom-Tapestry-2").eval()
183
+ eot = tok.convert_tokens_to_ids("<|eot|>")
184
+
185
+ def ask(message, tools=False):
186
+ p = f"<tools:{'on' if tools else 'off'}>\n<user>\n{message}\n<|eot|>\n<loom>\n"
187
+ ids = tok(p, return_tensors="pt", add_special_tokens=False).input_ids
188
+ with torch.no_grad():
189
+ out = model.generate(ids, max_new_tokens=64, do_sample=False, eos_token_id=eot,
190
+ pad_token_id=tok.convert_tokens_to_ids("<|pad|>"))[0]
191
+ return tok.decode(out[ids.shape[1]:], skip_special_tokens=True).strip()
192
+
193
+ ask("who are you") # -> 'Loom Tapestry 2, a small model by Textile Labs.'
194
+ ```
195
+
196
+ Prompt format is exact: `<tools:off>\n<user>\n{message}\n<|eot|>\n<loom>\n`.
197
+ No trailing space after `<loom>`.
198
+
199
+ ## How it was built
200
+
201
+ | | |
202
+ |---|---|
203
+ | architecture | Llama — 20 layers × 320d, GQA, SwiGLU, RoPE, tied embeddings |
204
+ | context | 768 |
205
+ | vocabulary | 4,096 custom BPE |
206
+ | optimiser | **Muon** on all 140 hidden matrices, AdamW on embeddings and norms |
207
+ | schedule | warmup → stable → decay (WSD) |
208
+ | corpus | 130,741 conversations · 17.8M tokens · **56% multi-turn** |
209
+ | training | 2,058 steps from random initialisation |
210
+
211
+ Depth was chosen over width deliberately: an earlier ladder study on this family found
212
+ that narrowing the hidden size cost about 3 points while removing a layer cost ten.
213
+
214
+ ## Files
215
+
216
+ ```
217
+ config.json / model.safetensors the model
218
+ tokenizer.json / tokenizer_config.json custom BPE tokenizer, 4,096 tokens
219
+ loom-tapestry-2-f16.gguf 44MB, for Ollama / llama.cpp
220
+ harness.py runnable harness — runs lookups, feeds results back
221
+ template / params read automatically by `ollama run hf.co/...`
222
+ Modelfile for building locally
223
+ ATTRIBUTION.md required credits for the training corpora
224
+ ```
225
+
226
+ ## Training data
227
+
228
+ Openly licensed corpora of real human text, plus a persona curriculum written for Loom.
229
+ See `ATTRIBUTION.md` — several of these licences require credit.
230
+
231
+ | slice | source |
232
+ |---|---|
233
+ | grounded reading, and "the result doesn't say" | **SQuAD 2.0** (CC BY-SA 4.0) |
234
+ | when to reach for a tool | **MASSIVE** (CC BY 4.0) · **CLINC150** (CC BY 3.0) |
235
+ | instruction following | **databricks-dolly-15k** (CC BY-SA 3.0) |
236
+ | multi-turn dialogue structure | **OpenAssistant OASST1** (Apache 2.0) |
237
+ | identity, limits, warmth, attribution | Textile Labs — written for Loom |
238
+
239
+ ## License
240
+
241
+ Model: MIT. Training data retains its original licences and attribution.
UPLOAD-STEPS.txt ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ LOOM TAPESTRY 2 — UPLOAD STEPS
2
+ ==============================
3
+
4
+ 1. Add your two images to this folder FIRST, named exactly:
5
+ banner.jpg
6
+ logo.jpg
7
+ (prompts are in TAPESTRY-2-ARTWORK-PROMPTS.md in the project folder.
8
+ The name must be .jpg — a .jpeg mismatch broke the images on an earlier release.)
9
+
10
+ 2. On huggingface.co, create a NEW model repo:
11
+ textilelabs/Loom-Tapestry-2
12
+ Owner: textilelabs · Public · License: MIT
13
+
14
+ 3. Files and versions -> Add file -> Upload files.
15
+ Drag in ALL the CONTENTS of this folder (not the folder itself).
16
+ Do NOT upload UPLOAD-STEPS.txt — it is for you, not the repo.
17
+
18
+ 4. Commit. That is it — no settings to change.
19
+ The tags, widget examples and licence all come from the top of README.md.
20
+
21
+ 5. Verify (takes a minute):
22
+ ollama run hf.co/textilelabs/Loom-Tapestry-2 "who are you"
23
+ Expected: "Loom Tapestry 2, a small model by Textile Labs." or similar.
24
+ If Ollama says it cannot find a template, the template/params files did not
25
+ upload — re-add just those two.
26
+
27
+ 6. Optional: add it to your "Loom-Spark" collection so it sits with the family.
28
+
29
+
30
+ WHAT IS IN HERE (14 files + your 2 images = 16)
31
+ -----------------------------------------------
32
+ README.md the model card (tags/widgets are in its header)
33
+ config.json model architecture
34
+ model.safetensors the weights, 87 MB
35
+ generation_config.json
36
+ tokenizer.json custom 4,096-token BPE
37
+ tokenizer_config.json
38
+ special_tokens_map.json
39
+ loom-tapestry-2-f16.gguf 44 MB, for Ollama / llama.cpp
40
+ template Ollama reads this automatically (defaults tools:off)
41
+ params Ollama reads this automatically
42
+ Modelfile only needed for `ollama create` locally
43
+ harness.py runnable harness — does the lookups
44
+ ATTRIBUTION.md licence credits, required by the corpora
45
+ LICENSE MIT
46
+ banner.jpg <- you add
47
+ logo.jpg <- you add
banner.jpg ADDED

Git LFS Details

  • SHA256: 664265e087b5fd45f4f38a6aa9f723bb33f0d78c804354883af90da5bc5ce305
  • Pointer size: 132 Bytes
  • Size of remote file: 2.26 MB
config.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "LlamaForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": null,
8
+ "dtype": "float32",
9
+ "eos_token_id": 0,
10
+ "head_dim": 64,
11
+ "hidden_act": "silu",
12
+ "hidden_size": 320,
13
+ "initializer_range": 0.02,
14
+ "intermediate_size": 864,
15
+ "max_position_embeddings": 768,
16
+ "mlp_bias": false,
17
+ "model_type": "llama",
18
+ "num_attention_heads": 5,
19
+ "num_hidden_layers": 20,
20
+ "num_key_value_heads": 1,
21
+ "pad_token_id": 1,
22
+ "pretraining_tp": 1,
23
+ "rms_norm_eps": 1e-05,
24
+ "rope_parameters": {
25
+ "rope_theta": 10000.0,
26
+ "rope_type": "default"
27
+ },
28
+ "tie_word_embeddings": true,
29
+ "transformers_version": "5.15.1",
30
+ "use_cache": true,
31
+ "vocab_size": 4096
32
+ }
generation_config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "eos_token_id": 0,
4
+ "output_attentions": false,
5
+ "output_hidden_states": false,
6
+ "pad_token_id": 1,
7
+ "transformers_version": "5.15.1",
8
+ "use_cache": true
9
+ }
harness.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Loom harness — runs the searches for Loom Spark 2.
3
+
4
+ The model never searches. It emits `<lookup>query</lookup>` and stops. This script
5
+ is the other half of the contract: it runs the lookup, feeds a `<result>` block
6
+ back, and lets the model answer from it.
7
+
8
+ user question
9
+ -> Loom (tools on) -> <lookup>who wrote Dracula</lookup>
10
+ -> harness runs Wikipedia
11
+ -> <result>...</result>
12
+ -> Loom -> Bram Stoker.
13
+
14
+ Wikipedia is used because it is free and needs no API key. Swap `search()` for
15
+ anything you like — the contract is just "text in, text out".
16
+
17
+ Usage:
18
+ python3 harness.py "who wrote Dracula"
19
+ python3 harness.py # interactive
20
+ python3 harness.py --no-tools "who are you"
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import argparse
25
+ import json
26
+ import re
27
+ import sys
28
+ import ssl
29
+ import urllib.parse
30
+ import urllib.request
31
+
32
+ # macOS system Python often ships without a usable CA bundle, so Wikipedia's TLS
33
+ # fails with CERTIFICATE_VERIFY_FAILED. Use certifi's bundle when it's available.
34
+ try:
35
+ import certifi
36
+ SSL_CTX = ssl.create_default_context(cafile=certifi.where())
37
+ except Exception:
38
+ SSL_CTX = ssl.create_default_context()
39
+
40
+ OLLAMA = "http://localhost:11434/api/generate"
41
+ MODEL = "hf.co/textilelabs/Loom-Tapestry-2"
42
+ LOOKUP = re.compile(r"<lookup>(.*?)</lookup>", re.S)
43
+ # Wikipedia returns 403 to requests without a descriptive User-Agent — their API
44
+ # policy requires one that identifies the client.
45
+ UA = {"User-Agent": "LoomHarness/1.0 (Textile Labs; loom harness demo)"}
46
+
47
+
48
+ def loom(prompt: str, n: int = 64) -> str:
49
+ """One raw generation. raw=True so our exact prompt format reaches the model."""
50
+ body = json.dumps({
51
+ "model": MODEL, "prompt": prompt, "raw": True, "stream": False,
52
+ "options": {"temperature": 0, "num_predict": n,
53
+ "stop": ["<|eot|>", "<user>", "<result>"]},
54
+ }).encode()
55
+ req = urllib.request.Request(OLLAMA, data=body,
56
+ headers={"Content-Type": "application/json"})
57
+ with urllib.request.urlopen(req, timeout=120) as r:
58
+ return json.load(r)["response"].strip()
59
+
60
+
61
+ def search(query: str, sentences: int = 3) -> str:
62
+ """Wikipedia lookup. Returns a short passage, or '' if nothing is found."""
63
+ api = "https://en.wikipedia.org/w/api.php?" + urllib.parse.urlencode({
64
+ "action": "query", "format": "json", "list": "search",
65
+ "srsearch": query, "srlimit": 1})
66
+ try:
67
+ with urllib.request.urlopen(urllib.request.Request(api, headers=UA),
68
+ timeout=20, context=SSL_CTX) as r:
69
+ hits = json.load(r)["query"]["search"]
70
+ if not hits:
71
+ return ""
72
+ title = hits[0]["title"]
73
+ summary = ("https://en.wikipedia.org/api/rest_v1/page/summary/"
74
+ + urllib.parse.quote(title, safe=""))
75
+ with urllib.request.urlopen(urllib.request.Request(summary, headers=UA),
76
+ timeout=20, context=SSL_CTX) as r:
77
+ extract = json.load(r).get("extract", "")
78
+ except Exception as e:
79
+ return f"(search failed: {e})"
80
+ parts = re.split(r"(?<=[.!?])\s+", extract)
81
+ return " ".join(parts[:sentences]).strip()
82
+
83
+
84
+ def ask(message: str, tools: bool = True, verbose: bool = True) -> str:
85
+ mode = "on" if tools else "off"
86
+ convo = f"<tools:{mode}>\n<user>\n{message.strip()}\n<|eot|>\n<loom>\n"
87
+ first = loom(convo)
88
+
89
+ m = LOOKUP.search(first)
90
+ if not m:
91
+ return first # answered directly, no tool wanted
92
+
93
+ query = m.group(1).strip()
94
+ if verbose:
95
+ print(f" [loom wants: {query!r}]")
96
+ result = search(query)
97
+ if not result or result.startswith("(search failed"):
98
+ # Never feed an error string in as if it were a result — the model will try
99
+ # to answer from it. Fail loudly instead.
100
+ return f"[harness] lookup failed for {query!r}: {result or 'no results'}"
101
+ if verbose:
102
+ print(f" [result: {result[:100]}...]")
103
+
104
+ convo += f"{first}<|eot|>\n<result>\n{result}\n<|eot|>\n<loom>\n"
105
+ return loom(convo, n=48)
106
+
107
+
108
+ def main():
109
+ ap = argparse.ArgumentParser()
110
+ ap.add_argument("message", nargs="*")
111
+ ap.add_argument("--no-tools", action="store_true", help="chat only, no lookups")
112
+ ap.add_argument("--quiet", action="store_true")
113
+ ap.add_argument("--model", default=MODEL)
114
+ args = ap.parse_args()
115
+ globals()["MODEL"] = args.model
116
+
117
+ if args.message:
118
+ print(ask(" ".join(args.message), not args.no_tools, not args.quiet))
119
+ return
120
+ print(f"Loom harness — {MODEL} (tools {'off' if args.no_tools else 'on'}, "
121
+ f"ctrl-c to quit)\n")
122
+ while True:
123
+ try:
124
+ msg = input("you > ").strip()
125
+ except (EOFError, KeyboardInterrupt):
126
+ print()
127
+ return
128
+ if msg:
129
+ print(f"loom > {ask(msg, not args.no_tools, not args.quiet)}\n")
130
+
131
+
132
+ if __name__ == "__main__":
133
+ sys.exit(main())
logo.jpg ADDED

Git LFS Details

  • SHA256: 454c14749563f6b160b9a9a44174779ca81ce9c94c20b9a8f77951759b675f2c
  • Pointer size: 132 Bytes
  • Size of remote file: 3.07 MB
loom-tapestry-2-f16.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b8ecb3aa4abd9ab9588515d646ea40d11ab191c5529662405e016774167db3a7
3
+ size 45820576
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:65cd9fc9fda0065735620158e3587a655d65e65f1abf965114b8295ff23bb20e
3
+ size 91331168
params ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stop": ["<|eot|>", "<user>", "<result>"],
3
+ "temperature": 0.7,
4
+ "top_k": 40,
5
+ "repeat_penalty": 1.15,
6
+ "repeat_last_n": 64,
7
+ "num_predict": 96
8
+ }
special_tokens_map.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "eos_token": "<|eot|>",
3
+ "pad_token": "<|pad|>",
4
+ "additional_special_tokens": [
5
+ "<tools:on>",
6
+ "<tools:off>",
7
+ "<user>",
8
+ "<loom>",
9
+ "<result>",
10
+ "<lookup>",
11
+ "</lookup>"
12
+ ]
13
+ }
template ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ <tools:off>
2
+ <user>
3
+ {{ .Prompt }}
4
+ <|eot|>
5
+ <loom>
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tokenizer_class": "PreTrainedTokenizerFast",
3
+ "model_max_length": 768,
4
+ "eos_token": "<|eot|>",
5
+ "pad_token": "<|pad|>",
6
+ "additional_special_tokens": [
7
+ "<tools:on>",
8
+ "<tools:off>",
9
+ "<user>",
10
+ "<loom>",
11
+ "<result>",
12
+ "<lookup>",
13
+ "</lookup>"
14
+ ],
15
+ "clean_up_tokenization_spaces": false
16
+ }