Update Open Structure release with v8 instruction benchmark wall
Browse files- README.md +14 -0
- docs/AETHON_OPEN_STRUCTURE_HF_MODEL_CARD.md +14 -0
- docs/AETHON_OPEN_STRUCTURE_RUNTIME.md +11 -5
- examples/aethon_open_structure_python.py +22 -0
- runtime/aethon/rfi_query.py +11 -0
- runtime/aethon/rfi_runtime.py +185 -2
- runtime/aethon/rfi_surface.py +4 -1
README.md
CHANGED
|
@@ -210,6 +210,7 @@ The intended public experience is model-like:
|
|
| 210 |
- load the bundle
|
| 211 |
- create a runtime object from the shipped release
|
| 212 |
- call `ask(...)`
|
|
|
|
| 213 |
- get natural text back
|
| 214 |
|
| 215 |
```python
|
|
@@ -222,6 +223,16 @@ try:
|
|
| 222 |
"Where is the notebook now, and explain the reasoning clearly."
|
| 223 |
)
|
| 224 |
print(reply.text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
finally:
|
| 226 |
model.close()
|
| 227 |
```
|
|
@@ -280,6 +291,7 @@ People should be able to ask in their own words.
|
|
| 280 |
| --- | --- | --- | --- |
|
| 281 |
| `aethon_n1_benchmark_v6.jsonl` | `43 / 43` | `1.0` | `3.476s` |
|
| 282 |
| `aethon_n1_benchmark_v7.jsonl` | `15 / 15` | `1.0` | `18.488s` |
|
|
|
|
| 283 |
|
| 284 |
### What This Wall Covers
|
| 285 |
|
|
@@ -291,6 +303,8 @@ People should be able to ask in their own words.
|
|
| 291 |
- open-grounded answers on unseen prompts
|
| 292 |
- religion transfer under fresh setup facts
|
| 293 |
- instruction-sensitive prompt checks
|
|
|
|
|
|
|
| 294 |
|
| 295 |
## One-Shot Data
|
| 296 |
|
|
|
|
| 210 |
- load the bundle
|
| 211 |
- create a runtime object from the shipped release
|
| 212 |
- call `ask(...)`
|
| 213 |
+
- or call `ask_messages([...])` for system-guided instruction following
|
| 214 |
- get natural text back
|
| 215 |
|
| 216 |
```python
|
|
|
|
| 223 |
"Where is the notebook now, and explain the reasoning clearly."
|
| 224 |
)
|
| 225 |
print(reply.text)
|
| 226 |
+
instructed = model.ask_messages(
|
| 227 |
+
[
|
| 228 |
+
{"role": "system", "content": "Answer in exactly three sentences and keep each sentence grounded."},
|
| 229 |
+
{
|
| 230 |
+
"role": "user",
|
| 231 |
+
"content": "Take this carefully and answer each part in one flowing response: where is Amina, what does regional launch depend on, and what is your tokenizer?",
|
| 232 |
+
},
|
| 233 |
+
]
|
| 234 |
+
)
|
| 235 |
+
print(instructed.text)
|
| 236 |
finally:
|
| 237 |
model.close()
|
| 238 |
```
|
|
|
|
| 291 |
| --- | --- | --- | --- |
|
| 292 |
| `aethon_n1_benchmark_v6.jsonl` | `43 / 43` | `1.0` | `3.476s` |
|
| 293 |
| `aethon_n1_benchmark_v7.jsonl` | `15 / 15` | `1.0` | `18.488s` |
|
| 294 |
+
| `aethon_n1_benchmark_v8.jsonl` | `10 / 10` | `1.0` | `89.170s` |
|
| 295 |
|
| 296 |
### What This Wall Covers
|
| 297 |
|
|
|
|
| 303 |
- open-grounded answers on unseen prompts
|
| 304 |
- religion transfer under fresh setup facts
|
| 305 |
- instruction-sensitive prompt checks
|
| 306 |
+
- native system-guided instruction following
|
| 307 |
+
- long mixed prompts with exact sentence-shape pressure
|
| 308 |
|
| 309 |
## One-Shot Data
|
| 310 |
|
docs/AETHON_OPEN_STRUCTURE_HF_MODEL_CARD.md
CHANGED
|
@@ -210,6 +210,7 @@ The intended public experience is model-like:
|
|
| 210 |
- load the bundle
|
| 211 |
- create a runtime object from the shipped release
|
| 212 |
- call `ask(...)`
|
|
|
|
| 213 |
- get natural text back
|
| 214 |
|
| 215 |
```python
|
|
@@ -222,6 +223,16 @@ try:
|
|
| 222 |
"Where is the notebook now, and explain the reasoning clearly."
|
| 223 |
)
|
| 224 |
print(reply.text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
finally:
|
| 226 |
model.close()
|
| 227 |
```
|
|
@@ -280,6 +291,7 @@ People should be able to ask in their own words.
|
|
| 280 |
| --- | --- | --- | --- |
|
| 281 |
| `aethon_n1_benchmark_v6.jsonl` | `43 / 43` | `1.0` | `3.476s` |
|
| 282 |
| `aethon_n1_benchmark_v7.jsonl` | `15 / 15` | `1.0` | `18.488s` |
|
|
|
|
| 283 |
|
| 284 |
### What This Wall Covers
|
| 285 |
|
|
@@ -291,6 +303,8 @@ People should be able to ask in their own words.
|
|
| 291 |
- open-grounded answers on unseen prompts
|
| 292 |
- religion transfer under fresh setup facts
|
| 293 |
- instruction-sensitive prompt checks
|
|
|
|
|
|
|
| 294 |
|
| 295 |
## One-Shot Data
|
| 296 |
|
|
|
|
| 210 |
- load the bundle
|
| 211 |
- create a runtime object from the shipped release
|
| 212 |
- call `ask(...)`
|
| 213 |
+
- or call `ask_messages([...])` for system-guided instruction following
|
| 214 |
- get natural text back
|
| 215 |
|
| 216 |
```python
|
|
|
|
| 223 |
"Where is the notebook now, and explain the reasoning clearly."
|
| 224 |
)
|
| 225 |
print(reply.text)
|
| 226 |
+
instructed = model.ask_messages(
|
| 227 |
+
[
|
| 228 |
+
{"role": "system", "content": "Answer in exactly three sentences and keep each sentence grounded."},
|
| 229 |
+
{
|
| 230 |
+
"role": "user",
|
| 231 |
+
"content": "Take this carefully and answer each part in one flowing response: where is Amina, what does regional launch depend on, and what is your tokenizer?",
|
| 232 |
+
},
|
| 233 |
+
]
|
| 234 |
+
)
|
| 235 |
+
print(instructed.text)
|
| 236 |
finally:
|
| 237 |
model.close()
|
| 238 |
```
|
|
|
|
| 291 |
| --- | --- | --- | --- |
|
| 292 |
| `aethon_n1_benchmark_v6.jsonl` | `43 / 43` | `1.0` | `3.476s` |
|
| 293 |
| `aethon_n1_benchmark_v7.jsonl` | `15 / 15` | `1.0` | `18.488s` |
|
| 294 |
+
| `aethon_n1_benchmark_v8.jsonl` | `10 / 10` | `1.0` | `89.170s` |
|
| 295 |
|
| 296 |
### What This Wall Covers
|
| 297 |
|
|
|
|
| 303 |
- open-grounded answers on unseen prompts
|
| 304 |
- religion transfer under fresh setup facts
|
| 305 |
- instruction-sensitive prompt checks
|
| 306 |
+
- native system-guided instruction following
|
| 307 |
+
- long mixed prompts with exact sentence-shape pressure
|
| 308 |
|
| 309 |
## One-Shot Data
|
| 310 |
|
docs/AETHON_OPEN_STRUCTURE_RUNTIME.md
CHANGED
|
@@ -53,17 +53,16 @@ The recommended public shape is:
|
|
| 53 |
1. pull the bundle
|
| 54 |
2. construct a runtime object from the shipped release
|
| 55 |
3. call `ask(...)`
|
| 56 |
-
4.
|
|
|
|
| 57 |
|
| 58 |
-
|
| 59 |
|
| 60 |
- `examples/aethon_open_structure_python.py`
|
| 61 |
- `run_aethon.py`
|
| 62 |
- `runtime/aethon/...`
|
| 63 |
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
That runtime hides storage details behind a model-facing interface so developers interact with Aethon as a model rather than as a data store.
|
| 67 |
|
| 68 |
## Minimum Read Path
|
| 69 |
|
|
@@ -101,6 +100,13 @@ model = AethonOpenStructureModel.from_hub("OkeyMetaLtd/Aethon-N1-Base-Open-Struc
|
|
| 101 |
try:
|
| 102 |
reply = model.ask("Tell me what changed about Amina's location and explain it clearly.")
|
| 103 |
print(reply.text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
finally:
|
| 105 |
model.close()
|
| 106 |
```
|
|
|
|
| 53 |
1. pull the bundle
|
| 54 |
2. construct a runtime object from the shipped release
|
| 55 |
3. call `ask(...)`
|
| 56 |
+
4. or call `ask_messages([...])` when a runtime wants system-style guidance
|
| 57 |
+
5. receive natural text back
|
| 58 |
|
| 59 |
+
Examples in this repo:
|
| 60 |
|
| 61 |
- `examples/aethon_open_structure_python.py`
|
| 62 |
- `run_aethon.py`
|
| 63 |
- `runtime/aethon/...`
|
| 64 |
|
| 65 |
+
These entry points expose Aethon as a model-facing runtime instead of a storage-facing interface.
|
|
|
|
|
|
|
| 66 |
|
| 67 |
## Minimum Read Path
|
| 68 |
|
|
|
|
| 100 |
try:
|
| 101 |
reply = model.ask("Tell me what changed about Amina's location and explain it clearly.")
|
| 102 |
print(reply.text)
|
| 103 |
+
instructed = model.ask_messages(
|
| 104 |
+
[
|
| 105 |
+
{"role": "system", "content": "Answer in exactly two sentences."},
|
| 106 |
+
{"role": "user", "content": "Where is Amina now, and what does regional launch depend on?"},
|
| 107 |
+
]
|
| 108 |
+
)
|
| 109 |
+
print(instructed.text)
|
| 110 |
finally:
|
| 111 |
model.close()
|
| 112 |
```
|
examples/aethon_open_structure_python.py
CHANGED
|
@@ -64,6 +64,17 @@ class AethonOpenStructureModel:
|
|
| 64 |
mode=response.mode,
|
| 65 |
)
|
| 66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
def learn(self, text: str) -> dict[str, object]:
|
| 68 |
return self._runtime.learn(text)
|
| 69 |
|
|
@@ -88,5 +99,16 @@ if __name__ == "__main__":
|
|
| 88 |
for step in reply.reasoning:
|
| 89 |
print(f" - {step}")
|
| 90 |
print()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
finally:
|
| 92 |
model.close()
|
|
|
|
| 64 |
mode=response.mode,
|
| 65 |
)
|
| 66 |
|
| 67 |
+
def ask_messages(self, messages: list[dict[str, str]]) -> AethonOpenStructureResponse:
|
| 68 |
+
response = self._runtime.ask_messages(messages)
|
| 69 |
+
return AethonOpenStructureResponse(
|
| 70 |
+
answer=response.answer,
|
| 71 |
+
text=response.text,
|
| 72 |
+
explanation=response.explanation,
|
| 73 |
+
proof=tuple(response.proof),
|
| 74 |
+
reasoning=tuple(response.reasoning),
|
| 75 |
+
mode=response.mode,
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
def learn(self, text: str) -> dict[str, object]:
|
| 79 |
return self._runtime.learn(text)
|
| 80 |
|
|
|
|
| 99 |
for step in reply.reasoning:
|
| 100 |
print(f" - {step}")
|
| 101 |
print()
|
| 102 |
+
instructed = model.ask_messages(
|
| 103 |
+
[
|
| 104 |
+
{"role": "system", "content": "Answer in exactly three sentences and keep each sentence grounded."},
|
| 105 |
+
{
|
| 106 |
+
"role": "user",
|
| 107 |
+
"content": "Take this carefully and answer each part in one flowing response: where is Amina, what does regional launch depend on, and what is your tokenizer?",
|
| 108 |
+
},
|
| 109 |
+
]
|
| 110 |
+
)
|
| 111 |
+
print("Instruction-following example:")
|
| 112 |
+
print(instructed.text)
|
| 113 |
finally:
|
| 114 |
model.close()
|
runtime/aethon/rfi_query.py
CHANGED
|
@@ -211,6 +211,10 @@ class ProofQueryEngine:
|
|
| 211 |
location = self._direct_or_abstract(parsed.subject, "located_in")
|
| 212 |
if location is not None:
|
| 213 |
return location
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
carried = self._infer_carried_object_location(parsed.subject)
|
| 215 |
if carried is not None:
|
| 216 |
return carried
|
|
@@ -996,6 +1000,9 @@ class ProofQueryEngine:
|
|
| 996 |
if lower_core in self._PROTECTED_QUERY_TOKENS:
|
| 997 |
corrected.append(token)
|
| 998 |
continue
|
|
|
|
|
|
|
|
|
|
| 999 |
if lower_core in self.ontology.semantic_lexicon.typo_map:
|
| 1000 |
replacement = self.ontology.semantic_lexicon.typo_map[lower_core]
|
| 1001 |
if core[:1].isupper():
|
|
@@ -1071,6 +1078,10 @@ class ProofQueryEngine:
|
|
| 1071 |
"divided",
|
| 1072 |
"by",
|
| 1073 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1074 |
for concept in self.graph.list_concepts():
|
| 1075 |
base_words.update(part for part in concept.split("_") if part)
|
| 1076 |
base_words.add(concept.replace("_", " "))
|
|
|
|
| 211 |
location = self._direct_or_abstract(parsed.subject, "located_in")
|
| 212 |
if location is not None:
|
| 213 |
return location
|
| 214 |
+
for relation in ("lives_in", "work_in", "study_in", "reached", "visited", "bought_in"):
|
| 215 |
+
direct_location = self._direct_or_abstract(parsed.subject, relation)
|
| 216 |
+
if direct_location is not None:
|
| 217 |
+
return direct_location
|
| 218 |
carried = self._infer_carried_object_location(parsed.subject)
|
| 219 |
if carried is not None:
|
| 220 |
return carried
|
|
|
|
| 1000 |
if lower_core in self._PROTECTED_QUERY_TOKENS:
|
| 1001 |
corrected.append(token)
|
| 1002 |
continue
|
| 1003 |
+
if lower_core in self.ontology.semantic_lexicon.alias_map:
|
| 1004 |
+
corrected.append(token)
|
| 1005 |
+
continue
|
| 1006 |
if lower_core in self.ontology.semantic_lexicon.typo_map:
|
| 1007 |
replacement = self.ontology.semantic_lexicon.typo_map[lower_core]
|
| 1008 |
if core[:1].isupper():
|
|
|
|
| 1078 |
"divided",
|
| 1079 |
"by",
|
| 1080 |
}
|
| 1081 |
+
base_words.update(self.math._NUMBER_WORDS.keys())
|
| 1082 |
+
base_words.update(self.ontology.semantic_lexicon.alias_map.keys())
|
| 1083 |
+
for phrase in self.ontology.semantic_lexicon.phrase_alias_map.keys():
|
| 1084 |
+
base_words.update(word for word in phrase.split() if word)
|
| 1085 |
for concept in self.graph.list_concepts():
|
| 1086 |
base_words.update(part for part in concept.split("_") if part)
|
| 1087 |
base_words.add(concept.replace("_", " "))
|
runtime/aethon/rfi_runtime.py
CHANGED
|
@@ -30,6 +30,13 @@ class NativeResponse:
|
|
| 30 |
mode: str
|
| 31 |
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
class AethonNativeBase:
|
| 34 |
"""The first real no-weight Aethon base runtime."""
|
| 35 |
|
|
@@ -233,9 +240,34 @@ class AethonNativeBase:
|
|
| 233 |
return {"rows": rows, "facts": facts}
|
| 234 |
|
| 235 |
def ask(self, query: str) -> NativeResponse:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
parts = self._split_query_parts(query)
|
| 237 |
if len(parts) > 1:
|
| 238 |
-
responses = [self.
|
| 239 |
return NativeResponse(
|
| 240 |
answer=" | ".join(response.answer for response in responses),
|
| 241 |
text=" ".join(response.text for response in responses if response.text),
|
|
@@ -257,6 +289,157 @@ class AethonNativeBase:
|
|
| 257 |
)
|
| 258 |
return self._render(query, result)
|
| 259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
def inspect(self, text: str) -> list[dict[str, object]]:
|
| 261 |
return self.codec.export_tokens(text)
|
| 262 |
|
|
@@ -293,7 +476,7 @@ class AethonNativeBase:
|
|
| 293 |
def _split_query_parts(query: str) -> list[str]:
|
| 294 |
parts: list[str] = []
|
| 295 |
for part in re.split(
|
| 296 |
-
r"(?:\?\s+|\?\s*$|(?:\s+and\s+also\s+)|(?:\s+also\s+)|(?:\s*;\s*)|(?:\s+then\s+)|(?:\r?\n+))",
|
| 297 |
query,
|
| 298 |
):
|
| 299 |
cleaned = part.strip()
|
|
|
|
| 30 |
mode: str
|
| 31 |
|
| 32 |
|
| 33 |
+
@dataclass(frozen=True)
|
| 34 |
+
class NativeInstructionProfile:
|
| 35 |
+
sentence_target: int | None = None
|
| 36 |
+
bullet_points: bool = False
|
| 37 |
+
answer_only: bool = False
|
| 38 |
+
|
| 39 |
+
|
| 40 |
class AethonNativeBase:
|
| 41 |
"""The first real no-weight Aethon base runtime."""
|
| 42 |
|
|
|
|
| 240 |
return {"rows": rows, "facts": facts}
|
| 241 |
|
| 242 |
def ask(self, query: str) -> NativeResponse:
|
| 243 |
+
profile, cleaned_query = self._extract_instruction_profile(query)
|
| 244 |
+
response = self._ask_core(cleaned_query)
|
| 245 |
+
return self._apply_instruction_profile(response, profile)
|
| 246 |
+
|
| 247 |
+
def ask_messages(self, messages: list[dict[str, str]]) -> NativeResponse:
|
| 248 |
+
system_parts: list[str] = []
|
| 249 |
+
user_query = ""
|
| 250 |
+
for message in messages:
|
| 251 |
+
role = str(message.get("role", "")).strip().lower()
|
| 252 |
+
content = str(message.get("content", "")).strip()
|
| 253 |
+
if not content:
|
| 254 |
+
continue
|
| 255 |
+
if role in {"system", "developer"}:
|
| 256 |
+
system_parts.append(content)
|
| 257 |
+
elif role == "user":
|
| 258 |
+
user_query = content
|
| 259 |
+
if not user_query:
|
| 260 |
+
return self.ask("")
|
| 261 |
+
system_profile, _ = self._extract_instruction_profile(" ".join(system_parts))
|
| 262 |
+
inline_profile, cleaned_query = self._extract_instruction_profile(user_query)
|
| 263 |
+
profile = self._merge_instruction_profiles(system_profile, inline_profile)
|
| 264 |
+
response = self._ask_core(cleaned_query)
|
| 265 |
+
return self._apply_instruction_profile(response, profile)
|
| 266 |
+
|
| 267 |
+
def _ask_core(self, query: str) -> NativeResponse:
|
| 268 |
parts = self._split_query_parts(query)
|
| 269 |
if len(parts) > 1:
|
| 270 |
+
responses = [self._ask_core(part) for part in parts]
|
| 271 |
return NativeResponse(
|
| 272 |
answer=" | ".join(response.answer for response in responses),
|
| 273 |
text=" ".join(response.text for response in responses if response.text),
|
|
|
|
| 289 |
)
|
| 290 |
return self._render(query, result)
|
| 291 |
|
| 292 |
+
def _apply_instruction_profile(self, response: NativeResponse, profile: NativeInstructionProfile) -> NativeResponse:
|
| 293 |
+
if not profile.answer_only and not profile.bullet_points and profile.sentence_target is None:
|
| 294 |
+
return response
|
| 295 |
+
text = response.text
|
| 296 |
+
explanation = response.explanation
|
| 297 |
+
if profile.answer_only:
|
| 298 |
+
text = self._answer_only_text(response)
|
| 299 |
+
explanation = text
|
| 300 |
+
elif profile.bullet_points:
|
| 301 |
+
text = self._bullet_text(response)
|
| 302 |
+
explanation = text
|
| 303 |
+
elif profile.sentence_target is not None:
|
| 304 |
+
text = self._reshape_to_sentence_target(response, profile.sentence_target)
|
| 305 |
+
explanation = text
|
| 306 |
+
return NativeResponse(
|
| 307 |
+
answer=response.answer,
|
| 308 |
+
text=text,
|
| 309 |
+
explanation=explanation,
|
| 310 |
+
proof=response.proof,
|
| 311 |
+
reasoning=response.reasoning,
|
| 312 |
+
mode=response.mode,
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
def _extract_instruction_profile(self, prompt: str) -> tuple[NativeInstructionProfile, str]:
|
| 316 |
+
lowered = prompt.lower()
|
| 317 |
+
sentence_target = self._sentence_target_from_prompt(lowered)
|
| 318 |
+
bullet_points = "bullet points" in lowered or "bullets" in lowered
|
| 319 |
+
answer_only = (
|
| 320 |
+
"only the final answer" in lowered
|
| 321 |
+
or "answer only" in lowered
|
| 322 |
+
or "final answer only" in lowered
|
| 323 |
+
)
|
| 324 |
+
cleaned = prompt.strip()
|
| 325 |
+
patterns = [
|
| 326 |
+
r"^(?:please\s+)?(?:answer|respond|write|use|give|provide)\s+(?:in\s+)?(?:exactly\s+)?(?:one|two|three|1|2|3)\s+sentences?\s*(?:and\s+)?",
|
| 327 |
+
r"^(?:please\s+)?(?:use|write|give|provide)\s+bullet\s+points?\s*(?:and\s+)?",
|
| 328 |
+
r"^(?:please\s+)?(?:answer|respond|write|give|provide)\s+with\s+only\s+the\s+final\s+answer[.:]?\s*",
|
| 329 |
+
r"^(?:please\s+)?(?:give|provide)\s+only\s+the\s+final\s+answer[.:]?\s*",
|
| 330 |
+
]
|
| 331 |
+
for pattern in patterns:
|
| 332 |
+
cleaned = re.sub(pattern, "", cleaned, flags=re.IGNORECASE)
|
| 333 |
+
if ":" in cleaned:
|
| 334 |
+
lead, tail = cleaned.split(":", 1)
|
| 335 |
+
if any(token in lead.lower() for token in ("answer each part", "flowing response", "carefully", "respond")) and tail.strip():
|
| 336 |
+
cleaned = tail.strip()
|
| 337 |
+
lowered_cleaned = cleaned.lower()
|
| 338 |
+
if lowered_cleaned.startswith("explain why "):
|
| 339 |
+
explanation_subject = cleaned[len("explain why ") :].strip()
|
| 340 |
+
for marker in (" equals ", "="):
|
| 341 |
+
if marker in explanation_subject:
|
| 342 |
+
explanation_subject = explanation_subject.split(marker, 1)[0].strip()
|
| 343 |
+
break
|
| 344 |
+
if explanation_subject:
|
| 345 |
+
cleaned = f"solve {explanation_subject}"
|
| 346 |
+
return NativeInstructionProfile(sentence_target=sentence_target, bullet_points=bullet_points, answer_only=answer_only), cleaned.strip() or prompt.strip()
|
| 347 |
+
|
| 348 |
+
@staticmethod
|
| 349 |
+
def _merge_instruction_profiles(left: NativeInstructionProfile, right: NativeInstructionProfile) -> NativeInstructionProfile:
|
| 350 |
+
return NativeInstructionProfile(
|
| 351 |
+
sentence_target=right.sentence_target if right.sentence_target is not None else left.sentence_target,
|
| 352 |
+
bullet_points=left.bullet_points or right.bullet_points,
|
| 353 |
+
answer_only=left.answer_only or right.answer_only,
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
@staticmethod
|
| 357 |
+
def _sentence_target_from_prompt(prompt: str) -> int | None:
|
| 358 |
+
match = re.search(r"(?:exactly\s+)?(one|two|three|1|2|3)\s+sentences?", prompt)
|
| 359 |
+
if match is None:
|
| 360 |
+
return None
|
| 361 |
+
token = match.group(1)
|
| 362 |
+
mapping = {"one": 1, "two": 2, "three": 3, "1": 1, "2": 2, "3": 3}
|
| 363 |
+
return mapping.get(token)
|
| 364 |
+
|
| 365 |
+
def _answer_only_text(self, response: NativeResponse) -> str:
|
| 366 |
+
parts = [part.strip() for part in response.answer.split("|") if part.strip()]
|
| 367 |
+
if not parts:
|
| 368 |
+
return response.text.strip()
|
| 369 |
+
humanized = [self.surface._humanize(part) for part in parts]
|
| 370 |
+
if len(humanized) == 1:
|
| 371 |
+
return humanized[0]
|
| 372 |
+
return "; ".join(humanized)
|
| 373 |
+
|
| 374 |
+
def _bullet_text(self, response: NativeResponse) -> str:
|
| 375 |
+
parts = [part.strip() for part in response.answer.split("|") if part.strip()]
|
| 376 |
+
if parts:
|
| 377 |
+
return "\n".join(f"- {self.surface._sentence(self.surface._humanize(part)).strip()}" for part in parts)
|
| 378 |
+
sentences = self._split_sentences(response.text)
|
| 379 |
+
if not sentences:
|
| 380 |
+
sentences = [response.text.strip()]
|
| 381 |
+
return "\n".join(f"- {self.surface._sentence(sentence).strip()}" for sentence in sentences if sentence.strip())
|
| 382 |
+
|
| 383 |
+
def _reshape_to_sentence_target(self, response: NativeResponse, target: int) -> str:
|
| 384 |
+
parts = [part.strip() for part in response.answer.split("|") if part.strip()]
|
| 385 |
+
if parts:
|
| 386 |
+
part_sentences = [self.surface._sentence(self.surface._humanize(part)).strip() for part in parts]
|
| 387 |
+
if target == 1:
|
| 388 |
+
return self.surface._sentence("; ".join(self.surface._humanize(part) for part in parts)).strip()
|
| 389 |
+
if len(part_sentences) >= target:
|
| 390 |
+
return " ".join(part_sentences[:target])
|
| 391 |
+
if response.mode == "story":
|
| 392 |
+
story_sentences = [
|
| 393 |
+
self.surface._sentence(sentence).strip()
|
| 394 |
+
for sentence in (
|
| 395 |
+
self.surface._proof_line_to_sentence(step) for step in response.proof
|
| 396 |
+
)
|
| 397 |
+
if sentence
|
| 398 |
+
]
|
| 399 |
+
if len(story_sentences) < target:
|
| 400 |
+
story_sentences.extend(self._split_sentences(response.text))
|
| 401 |
+
if len(story_sentences) >= target:
|
| 402 |
+
return " ".join(story_sentences[-target:])
|
| 403 |
+
if response.mode == "plan":
|
| 404 |
+
answer_sentence = self.surface._sentence(self.surface._humanize(response.answer)).strip()
|
| 405 |
+
support_candidates = [sentence for sentence in self._candidate_sentences(response) if self.surface._humanize(response.answer).lower() not in sentence.lower()]
|
| 406 |
+
if target == 1:
|
| 407 |
+
return answer_sentence
|
| 408 |
+
selected = [answer_sentence]
|
| 409 |
+
selected.extend(support_candidates[: max(target - 1, 0)])
|
| 410 |
+
while len(selected) < target:
|
| 411 |
+
selected.append(answer_sentence)
|
| 412 |
+
return " ".join(selected[:target])
|
| 413 |
+
candidates = self._candidate_sentences(response)
|
| 414 |
+
if not candidates:
|
| 415 |
+
return response.text.strip()
|
| 416 |
+
if target == 1:
|
| 417 |
+
return self.surface._sentence(" ".join(sentence.rstrip(".!?") for sentence in candidates[:2])).strip()
|
| 418 |
+
selected = candidates[:target]
|
| 419 |
+
while len(selected) < target:
|
| 420 |
+
selected.append(self.surface._sentence(self.surface._humanize(response.answer)).strip())
|
| 421 |
+
return " ".join(selected[:target])
|
| 422 |
+
|
| 423 |
+
def _candidate_sentences(self, response: NativeResponse) -> list[str]:
|
| 424 |
+
candidates: list[str] = []
|
| 425 |
+
seen: set[str] = set()
|
| 426 |
+
for text in [response.text, response.explanation, *response.reasoning]:
|
| 427 |
+
for sentence in self._split_sentences(text):
|
| 428 |
+
normalized = sentence.strip()
|
| 429 |
+
if normalized and normalized not in seen:
|
| 430 |
+
candidates.append(self.surface._sentence(normalized).strip())
|
| 431 |
+
seen.add(normalized)
|
| 432 |
+
if response.answer and response.answer != "<unknown>":
|
| 433 |
+
normalized_answer = self.surface._humanize(response.answer)
|
| 434 |
+
if normalized_answer not in seen:
|
| 435 |
+
candidates.append(self.surface._sentence(normalized_answer).strip())
|
| 436 |
+
return candidates
|
| 437 |
+
|
| 438 |
+
@staticmethod
|
| 439 |
+
def _split_sentences(text: str) -> list[str]:
|
| 440 |
+
return [piece.strip() for piece in re.split(r"(?<=[.!?])\s+", text.strip()) if piece.strip()]
|
| 441 |
+
|
| 442 |
+
|
| 443 |
def inspect(self, text: str) -> list[dict[str, object]]:
|
| 444 |
return self.codec.export_tokens(text)
|
| 445 |
|
|
|
|
| 476 |
def _split_query_parts(query: str) -> list[str]:
|
| 477 |
parts: list[str] = []
|
| 478 |
for part in re.split(
|
| 479 |
+
r"(?:\?\s+|\?\s*$|(?:\s+and\s+also\s+)|(?:\s+also\s+)|(?:\s*;\s*)|(?:\s+then\s+)|(?:,\s+(?=what|where|who|how|why|which|solve))|(?:\s+and\s+(?=what|where|who|how|why|which|solve))|(?:\r?\n+))",
|
| 480 |
query,
|
| 481 |
):
|
| 482 |
cleaned = part.strip()
|
runtime/aethon/rfi_surface.py
CHANGED
|
@@ -457,13 +457,16 @@ class GraphVerbalizer:
|
|
| 457 |
return []
|
| 458 |
concepts = self._unknown_query_concepts(query)
|
| 459 |
query_kind = self._unknown_query_kind(query)
|
|
|
|
| 460 |
supports: list[str] = []
|
| 461 |
seen: set[str] = set()
|
| 462 |
for concept in concepts[:2]:
|
| 463 |
edges = [
|
| 464 |
edge
|
| 465 |
for edge in self.graph.iter_outgoing_edges(concept)
|
| 466 |
-
if edge.is_active and
|
|
|
|
|
|
|
| 467 |
]
|
| 468 |
edges.sort(key=lambda edge: (0 if edge.source_kind != "derived" else 1, -edge.edge_id))
|
| 469 |
for edge in edges[:2]:
|
|
|
|
| 457 |
return []
|
| 458 |
concepts = self._unknown_query_concepts(query)
|
| 459 |
query_kind = self._unknown_query_kind(query)
|
| 460 |
+
lowered_query = query.lower()
|
| 461 |
supports: list[str] = []
|
| 462 |
seen: set[str] = set()
|
| 463 |
for concept in concepts[:2]:
|
| 464 |
edges = [
|
| 465 |
edge
|
| 466 |
for edge in self.graph.iter_outgoing_edges(concept)
|
| 467 |
+
if edge.is_active and (
|
| 468 |
+
"what changed about " in lowered_query or self._edge_matches_unknown_query_kind(edge.relation, query_kind)
|
| 469 |
+
)
|
| 470 |
]
|
| 471 |
edges.sort(key=lambda edge: (0 if edge.source_kind != "derived" else 1, -edge.edge_id))
|
| 472 |
for edge in edges[:2]:
|