dirac-lang 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LLM-VALIDATION.md +128 -0
- package/TESTING.md +162 -0
- package/dist/{chunk-VHP3G4BF.js → chunk-3LRJLSZC.js} +1 -1
- package/dist/{chunk-X3LQ626H.js → chunk-UDA4H3GU.js} +91 -11
- package/dist/cli.js +6 -5
- package/dist/index.js +2 -2
- package/dist/{interpreter-46CAOCAZ.js → interpreter-BYQIE2MI.js} +1 -1
- package/dist/tag-validator-I3GLCBVD.js +152 -0
- package/dist/test-runner.d.ts +42 -0
- package/dist/test-runner.js +171 -0
- package/examples/llm-validate-test.di +18 -0
- package/package.json +4 -3
- package/src/tags/llm.ts +100 -2
- package/src/test-runner.ts +226 -0
- package/src/utils/tag-validator.ts +227 -0
- package/tests/basic-output.test.di +5 -0
- package/tests/exception-basic.test.di +10 -0
- package/tests/if-conditional.test.di +17 -0
- package/tests/loop-basic.test.di +9 -0
- package/tests/subroutine-basic.test.di +9 -0
- package/tests/test-if-basic.test.di +8 -0
- package/tests/variable-basic.test.di +6 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# LLM Tag Validation
|
|
2
|
+
|
|
3
|
+
The `<llm>` tag now supports automatic validation and correction of generated Dirac code when using `execute="true"`.
|
|
4
|
+
|
|
5
|
+
## Attributes
|
|
6
|
+
|
|
7
|
+
### Execution Mode
|
|
8
|
+
- `execute="true"` - Parse and execute the LLM response as Dirac code (existing feature)
|
|
9
|
+
|
|
10
|
+
### Tag Validation (New)
|
|
11
|
+
- `validate="true"` - Enable tag validation for LLM-generated code
|
|
12
|
+
- `autocorrect="true"` - Automatically correct similar tag names using semantic matching
|
|
13
|
+
- `max-retries="N"` - Maximum number of retry attempts if validation fails (default: 0)
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
16
|
+
|
|
17
|
+
When `validate="true"` is enabled:
|
|
18
|
+
|
|
19
|
+
1. **Parse**: LLM response is parsed as Dirac XML
|
|
20
|
+
2. **Validate**: Each tag is checked against available subroutines
|
|
21
|
+
- Verifies tag names exist
|
|
22
|
+
- Checks required parameters are present
|
|
23
|
+
- Warns about unknown attributes
|
|
24
|
+
3. **Semantic Matching**: If a tag doesn't exist, finds the closest match using embeddings
|
|
25
|
+
4. **Auto-correct**: If `autocorrect="true"` and similarity >= 0.75, replaces tag with best match
|
|
26
|
+
5. **Retry**: If validation fails and `max-retries > 0`, sends error feedback to LLM and retries
|
|
27
|
+
6. **Execute**: Once validation passes, executes the (possibly corrected) code
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Basic Validation
|
|
32
|
+
|
|
33
|
+
```xml
|
|
34
|
+
<dirac>
|
|
35
|
+
<subroutine name="greet" param-name="string:required">
|
|
36
|
+
<output>Hello, <variable name="name" />!</output>
|
|
37
|
+
</subroutine>
|
|
38
|
+
|
|
39
|
+
<llm execute="true" validate="true">
|
|
40
|
+
Greet Alice
|
|
41
|
+
</llm>
|
|
42
|
+
</dirac>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If the LLM generates `<greeting name="Alice" />` instead of `<greet name="Alice" />`, validation will fail with an error.
|
|
46
|
+
|
|
47
|
+
### With Auto-correction
|
|
48
|
+
|
|
49
|
+
```xml
|
|
50
|
+
<dirac>
|
|
51
|
+
<subroutine name="greet" param-name="string:required">
|
|
52
|
+
<output>Hello, <variable name="name" />!</output>
|
|
53
|
+
</subroutine>
|
|
54
|
+
|
|
55
|
+
<llm execute="true" validate="true" autocorrect="true">
|
|
56
|
+
Greet Alice
|
|
57
|
+
</llm>
|
|
58
|
+
</dirac>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If the LLM generates `<greeting name="Alice" />`, and `greeting` is semantically similar to `greet` (similarity >= 0.75), it will be auto-corrected to `<greet name="Alice" />`.
|
|
62
|
+
|
|
63
|
+
### With Retry
|
|
64
|
+
|
|
65
|
+
```xml
|
|
66
|
+
<dirac>
|
|
67
|
+
<subroutine name="calculate" param-expression="string:required">
|
|
68
|
+
<eval><variable name="expression" /></eval>
|
|
69
|
+
</subroutine>
|
|
70
|
+
|
|
71
|
+
<llm execute="true" validate="true" max-retries="3">
|
|
72
|
+
Calculate 2 + 2
|
|
73
|
+
</llm>
|
|
74
|
+
</dirac>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If validation fails:
|
|
78
|
+
1. LLM receives feedback: "Your previous response had the following errors: <compute>: Missing required parameter: expression"
|
|
79
|
+
2. LLM generates a new response
|
|
80
|
+
3. Process repeats up to 3 times until validation passes
|
|
81
|
+
|
|
82
|
+
### Combined Approach
|
|
83
|
+
|
|
84
|
+
```xml
|
|
85
|
+
<llm execute="true" validate="true" autocorrect="true" max-retries="2">
|
|
86
|
+
Generate a greeting for Bob
|
|
87
|
+
</llm>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This combines auto-correction with retry:
|
|
91
|
+
- First tries to auto-correct similar tag names
|
|
92
|
+
- If that doesn't fix all errors, retries with LLM feedback
|
|
93
|
+
- Maximum 2 retry attempts
|
|
94
|
+
|
|
95
|
+
## Error Messages
|
|
96
|
+
|
|
97
|
+
Validation can detect:
|
|
98
|
+
|
|
99
|
+
- **Missing tags**: `Tag <xyz> does not exist and no similar tag was found.`
|
|
100
|
+
- **Similar tags**: `Tag <greeting> does not exist. Did you mean <greet>? (similarity: 0.85)`
|
|
101
|
+
- **Missing parameters**: `<greet>: Missing required parameter: name`
|
|
102
|
+
- **Unknown attributes**: `<greet>: Unknown attribute: person`
|
|
103
|
+
|
|
104
|
+
## Requirements
|
|
105
|
+
|
|
106
|
+
- Requires embedding server for semantic matching (Ollama with embeddinggemma model)
|
|
107
|
+
- Configure in `config.yml`:
|
|
108
|
+
```yaml
|
|
109
|
+
embeddingServer:
|
|
110
|
+
host: localhost
|
|
111
|
+
port: 11435
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Performance Notes
|
|
115
|
+
|
|
116
|
+
- Validation adds latency due to embedding calls
|
|
117
|
+
- Each tag requires an embedding API call
|
|
118
|
+
- Consider using `validate="true"` only when necessary
|
|
119
|
+
- Auto-correction is faster than retries
|
|
120
|
+
|
|
121
|
+
## Best Practices
|
|
122
|
+
|
|
123
|
+
1. **Start without validation** for simple prompts
|
|
124
|
+
2. **Add validation** when LLM frequently generates incorrect tags
|
|
125
|
+
3. **Use autocorrect** for typos and similar names
|
|
126
|
+
4. **Use retry** for more complex validation errors
|
|
127
|
+
5. **Limit retries** to 2-3 to avoid excessive API calls
|
|
128
|
+
6. **Monitor debug output** with `DIRAC_DEBUG=1` to see validation details
|
package/TESTING.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Dirac Testing System
|
|
2
|
+
|
|
3
|
+
A simple, lightweight testing framework for Dirac language files.
|
|
4
|
+
|
|
5
|
+
## Running Tests
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm test
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This will:
|
|
12
|
+
1. Build the project
|
|
13
|
+
2. Run all `*.test.di` files in the `tests/` directory
|
|
14
|
+
3. Report results
|
|
15
|
+
|
|
16
|
+
## Writing Tests
|
|
17
|
+
|
|
18
|
+
Tests are standard Dirac `.di` files with special comments that define expectations.
|
|
19
|
+
|
|
20
|
+
### Test File Format
|
|
21
|
+
|
|
22
|
+
```xml
|
|
23
|
+
<!-- TEST: test_name -->
|
|
24
|
+
<!-- EXPECT: expected output -->
|
|
25
|
+
<dirac>
|
|
26
|
+
<!-- your test code here -->
|
|
27
|
+
</dirac>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Test Metadata Comments
|
|
31
|
+
|
|
32
|
+
- `<!-- TEST: name -->` - Name of the test (optional, defaults to filename)
|
|
33
|
+
- `<!-- EXPECT: output -->` - Expected output (optional)
|
|
34
|
+
- `<!-- EXPECT_ERROR: error message -->` - Expected error message (optional)
|
|
35
|
+
|
|
36
|
+
### Example Tests
|
|
37
|
+
|
|
38
|
+
#### Basic Output Test
|
|
39
|
+
|
|
40
|
+
```xml
|
|
41
|
+
<!-- TEST: hello_world -->
|
|
42
|
+
<!-- EXPECT: Hello, World! -->
|
|
43
|
+
<dirac>
|
|
44
|
+
<output>Hello, World!</output>
|
|
45
|
+
</dirac>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### Variable Test
|
|
49
|
+
|
|
50
|
+
```xml
|
|
51
|
+
<!-- TEST: variables -->
|
|
52
|
+
<!-- EXPECT: Value is: test123 -->
|
|
53
|
+
<dirac>
|
|
54
|
+
<defvar name="myvar" value="test123" />
|
|
55
|
+
<output>Value is: <variable name="myvar" /></output>
|
|
56
|
+
</dirac>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Error Test
|
|
60
|
+
|
|
61
|
+
```xml
|
|
62
|
+
<!-- TEST: missing_variable -->
|
|
63
|
+
<!-- EXPECT_ERROR: Variable 'missing' not found -->
|
|
64
|
+
<dirac>
|
|
65
|
+
<variable name="missing" />
|
|
66
|
+
</dirac>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### No Expectation (Just Run)
|
|
70
|
+
|
|
71
|
+
```xml
|
|
72
|
+
<!-- TEST: runs_without_error -->
|
|
73
|
+
<dirac>
|
|
74
|
+
<defvar name="x" value="10" />
|
|
75
|
+
<!-- Test passes if it runs without throwing an error -->
|
|
76
|
+
</dirac>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Test Organization
|
|
80
|
+
|
|
81
|
+
Tests should be placed in the `tests/` directory with the `.test.di` extension:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
tests/
|
|
85
|
+
├── basic-output.test.di
|
|
86
|
+
├── variable-basic.test.di
|
|
87
|
+
├── subroutine-basic.test.di
|
|
88
|
+
├── if-conditional.test.di
|
|
89
|
+
└── ...
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You can organize tests into subdirectories:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
tests/
|
|
96
|
+
├── core/
|
|
97
|
+
│ ├── output.test.di
|
|
98
|
+
│ └── variables.test.di
|
|
99
|
+
├── control-flow/
|
|
100
|
+
│ ├── if.test.di
|
|
101
|
+
│ └── loop.test.di
|
|
102
|
+
└── llm/
|
|
103
|
+
└── basic.test.di
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Output Matching
|
|
107
|
+
|
|
108
|
+
The test runner normalizes whitespace when comparing output:
|
|
109
|
+
- Multiple spaces/newlines are collapsed to single spaces
|
|
110
|
+
- Leading/trailing whitespace is trimmed
|
|
111
|
+
- This allows tests to ignore XML formatting whitespace
|
|
112
|
+
|
|
113
|
+
For example, these are equivalent:
|
|
114
|
+
- Expected: `Hello World`
|
|
115
|
+
- Actual: ` Hello World ` → matches ✓
|
|
116
|
+
- Actual: `Hello\n World` → matches ✓
|
|
117
|
+
|
|
118
|
+
## Exit Codes
|
|
119
|
+
|
|
120
|
+
- `0` - All tests passed
|
|
121
|
+
- `1` - One or more tests failed
|
|
122
|
+
|
|
123
|
+
## Continuous Integration
|
|
124
|
+
|
|
125
|
+
Add to your CI pipeline:
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
# .github/workflows/test.yml
|
|
129
|
+
- name: Run tests
|
|
130
|
+
run: npm test
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Best Practices
|
|
134
|
+
|
|
135
|
+
1. **One concept per test** - Each test should verify one specific behavior
|
|
136
|
+
2. **Descriptive names** - Use clear test names that describe what's being tested
|
|
137
|
+
3. **Test edge cases** - Include tests for error conditions and boundary cases
|
|
138
|
+
4. **Keep tests fast** - Avoid LLM calls in unit tests (use mocks or separate integration tests)
|
|
139
|
+
5. **Test regressions** - When fixing bugs, add a test to prevent regression
|
|
140
|
+
|
|
141
|
+
## Example Test Suite
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
tests/
|
|
145
|
+
├── basic-output.test.di # Basic output functionality
|
|
146
|
+
├── variable-basic.test.di # Variable definition and output
|
|
147
|
+
├── subroutine-basic.test.di # Subroutine calls
|
|
148
|
+
├── if-conditional.test.di # Conditional execution
|
|
149
|
+
├── loop-basic.test.di # Loop functionality
|
|
150
|
+
├── exception-basic.test.di # Exception handling
|
|
151
|
+
└── test-if-basic.test.di # test-if conditional
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Future Enhancements
|
|
155
|
+
|
|
156
|
+
Potential improvements:
|
|
157
|
+
- Watch mode for TDD workflow
|
|
158
|
+
- Code coverage reporting
|
|
159
|
+
- Performance benchmarks
|
|
160
|
+
- Parallel test execution
|
|
161
|
+
- Test fixtures/setup/teardown
|
|
162
|
+
- Snapshot testing for complex outputs
|
|
@@ -370,12 +370,12 @@ async function executeIf(session, element) {
|
|
|
370
370
|
const condition = await evaluatePredicate(session, conditionElement);
|
|
371
371
|
if (condition) {
|
|
372
372
|
if (thenElement) {
|
|
373
|
-
const { integrateChildren: integrateChildren2 } = await import("./interpreter-
|
|
373
|
+
const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
|
|
374
374
|
await integrateChildren2(session, thenElement);
|
|
375
375
|
}
|
|
376
376
|
} else {
|
|
377
377
|
if (elseElement) {
|
|
378
|
-
const { integrateChildren: integrateChildren2 } = await import("./interpreter-
|
|
378
|
+
const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
|
|
379
379
|
await integrateChildren2(session, elseElement);
|
|
380
380
|
}
|
|
381
381
|
}
|
|
@@ -388,7 +388,7 @@ async function evaluatePredicate(session, predicateElement) {
|
|
|
388
388
|
return await evaluateCondition(session, predicateElement);
|
|
389
389
|
}
|
|
390
390
|
const outputLengthBefore = session.output.length;
|
|
391
|
-
const { integrate: integrate2 } = await import("./interpreter-
|
|
391
|
+
const { integrate: integrate2 } = await import("./interpreter-BYQIE2MI.js");
|
|
392
392
|
await integrate2(session, predicateElement);
|
|
393
393
|
const newOutputChunks = session.output.slice(outputLengthBefore);
|
|
394
394
|
const result = newOutputChunks.join("").trim();
|
|
@@ -411,11 +411,11 @@ async function evaluateCondition(session, condElement) {
|
|
|
411
411
|
}
|
|
412
412
|
const outputLengthBefore = session.output.length;
|
|
413
413
|
const args = [];
|
|
414
|
-
const { integrate: integrate2 } = await import("./interpreter-
|
|
414
|
+
const { integrate: integrate2 } = await import("./interpreter-BYQIE2MI.js");
|
|
415
415
|
for (const child of condElement.children) {
|
|
416
416
|
if (child.tag.toLowerCase() === "arg") {
|
|
417
417
|
const argOutputStart = session.output.length;
|
|
418
|
-
const { integrateChildren: integrateChildren2 } = await import("./interpreter-
|
|
418
|
+
const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
|
|
419
419
|
await integrateChildren2(session, child);
|
|
420
420
|
const newChunks = session.output.slice(argOutputStart);
|
|
421
421
|
const argValue = newChunks.join("");
|
|
@@ -724,10 +724,16 @@ then you call it like
|
|
|
724
724
|
if (outputVar) {
|
|
725
725
|
setVariable(session, outputVar, result, false);
|
|
726
726
|
} else if (executeMode) {
|
|
727
|
+
const validateTags = element.attributes["validate"] === "true";
|
|
728
|
+
const autocorrect = element.attributes["autocorrect"] === "true";
|
|
729
|
+
const maxRetries = parseInt(element.attributes["max-retries"] || "0", 10);
|
|
727
730
|
if (session.debug) {
|
|
728
731
|
console.error(`[LLM] Executing response as Dirac code:
|
|
729
732
|
${result}
|
|
730
733
|
`);
|
|
734
|
+
if (validateTags) {
|
|
735
|
+
console.error(`[LLM] Tag validation enabled (autocorrect: ${autocorrect}, max-retries: ${maxRetries})`);
|
|
736
|
+
}
|
|
731
737
|
}
|
|
732
738
|
const replaceTick = element.attributes["replace-tick"] === "true";
|
|
733
739
|
let diracCode = result.trim();
|
|
@@ -743,7 +749,81 @@ ${result}
|
|
|
743
749
|
}
|
|
744
750
|
try {
|
|
745
751
|
const parser = new DiracParser();
|
|
746
|
-
|
|
752
|
+
let dynamicAST = parser.parse(diracCode);
|
|
753
|
+
if (validateTags) {
|
|
754
|
+
const { validateDiracCode, applyCorrectedTags } = await import("./tag-validator-I3GLCBVD.js");
|
|
755
|
+
let validation = await validateDiracCode(session, dynamicAST, { autocorrect });
|
|
756
|
+
let retryCount = 0;
|
|
757
|
+
while (!validation.valid && retryCount < maxRetries) {
|
|
758
|
+
retryCount++;
|
|
759
|
+
if (session.debug) {
|
|
760
|
+
console.error(`[LLM] Validation failed (attempt ${retryCount}/${maxRetries}):`, validation.errorMessages);
|
|
761
|
+
}
|
|
762
|
+
const errorFeedback = validation.errorMessages.join("\n");
|
|
763
|
+
const retryPrompt = `Your previous response had the following errors:
|
|
764
|
+
${errorFeedback}
|
|
765
|
+
|
|
766
|
+
Please fix these errors and generate valid Dirac XML again. Remember to only use the allowed tags.`;
|
|
767
|
+
dialogHistory.push({ role: "user", content: retryPrompt });
|
|
768
|
+
if (isOpenAI) {
|
|
769
|
+
const response = await session.llmClient.chat.completions.create({
|
|
770
|
+
model,
|
|
771
|
+
max_tokens: maxTokens,
|
|
772
|
+
temperature,
|
|
773
|
+
messages: dialogHistory
|
|
774
|
+
});
|
|
775
|
+
result = response.choices[0]?.message?.content || "";
|
|
776
|
+
} else if (isOllama) {
|
|
777
|
+
const ollamaPrompt = dialogHistory.map((m) => `${m.role.charAt(0).toUpperCase() + m.role.slice(1)}: ${m.content}`).join("\n");
|
|
778
|
+
result = await session.llmClient.complete(ollamaPrompt, {
|
|
779
|
+
model,
|
|
780
|
+
temperature,
|
|
781
|
+
max_tokens: maxTokens
|
|
782
|
+
});
|
|
783
|
+
} else {
|
|
784
|
+
const response = await session.llmClient.messages.create({
|
|
785
|
+
model,
|
|
786
|
+
max_tokens: maxTokens,
|
|
787
|
+
temperature,
|
|
788
|
+
messages: dialogHistory
|
|
789
|
+
});
|
|
790
|
+
const content = response.content[0];
|
|
791
|
+
result = content.type === "text" ? content.text : "";
|
|
792
|
+
}
|
|
793
|
+
dialogHistory.push({ role: "assistant", content: result });
|
|
794
|
+
if (contextVar) {
|
|
795
|
+
setVariable(session, contextVar, dialogHistory, true);
|
|
796
|
+
}
|
|
797
|
+
if (session.debug) {
|
|
798
|
+
console.error(`[LLM] Retry ${retryCount} response:
|
|
799
|
+
${result}
|
|
800
|
+
`);
|
|
801
|
+
}
|
|
802
|
+
diracCode = result.trim();
|
|
803
|
+
if (replaceTick && diracCode.startsWith("```")) {
|
|
804
|
+
const match = diracCode.match(/^```(\w+)?\n?/m);
|
|
805
|
+
if (match && match[1] === "bash") {
|
|
806
|
+
const endIdx = diracCode.indexOf("```", 3);
|
|
807
|
+
let bashContent = diracCode.slice(match[0].length, endIdx).trim();
|
|
808
|
+
diracCode = `<system>${bashContent}</system>`;
|
|
809
|
+
} else {
|
|
810
|
+
diracCode = diracCode.replace(/^```(?:xml|html|dirac)?\n?/m, "").replace(/\n?```$/m, "").trim();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
dynamicAST = parser.parse(diracCode);
|
|
814
|
+
validation = await validateDiracCode(session, dynamicAST, { autocorrect });
|
|
815
|
+
}
|
|
816
|
+
if (!validation.valid) {
|
|
817
|
+
throw new Error(`Tag validation failed after ${maxRetries} retries:
|
|
818
|
+
${validation.errorMessages.join("\n")}`);
|
|
819
|
+
}
|
|
820
|
+
if (autocorrect) {
|
|
821
|
+
dynamicAST = applyCorrectedTags(dynamicAST, validation.results);
|
|
822
|
+
if (session.debug) {
|
|
823
|
+
console.error("[LLM] Applied auto-corrections to tags");
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
747
827
|
await integrate(session, dynamicAST);
|
|
748
828
|
} catch (parseError) {
|
|
749
829
|
if (session.debug) {
|
|
@@ -1162,7 +1242,7 @@ async function executeTagCheck(session, element) {
|
|
|
1162
1242
|
const executeTag = correctedTag || tagName;
|
|
1163
1243
|
console.error(`[tag-check] Executing <${executeTag}/> as all checks passed and execute=true.`);
|
|
1164
1244
|
const elementToExecute = correctedTag ? { ...child, tag: correctedTag } : child;
|
|
1165
|
-
const { integrate: integrate2 } = await import("./interpreter-
|
|
1245
|
+
const { integrate: integrate2 } = await import("./interpreter-BYQIE2MI.js");
|
|
1166
1246
|
await integrate2(session, elementToExecute);
|
|
1167
1247
|
}
|
|
1168
1248
|
}
|
|
@@ -1171,7 +1251,7 @@ async function executeTagCheck(session, element) {
|
|
|
1171
1251
|
// src/tags/throw.ts
|
|
1172
1252
|
async function executeThrow(session, element) {
|
|
1173
1253
|
const exceptionName = element.attributes?.name || "exception";
|
|
1174
|
-
const { integrateChildren: integrateChildren2 } = await import("./interpreter-
|
|
1254
|
+
const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
|
|
1175
1255
|
const exceptionDom = {
|
|
1176
1256
|
tag: "exception-content",
|
|
1177
1257
|
attributes: { name: exceptionName },
|
|
@@ -1184,7 +1264,7 @@ async function executeThrow(session, element) {
|
|
|
1184
1264
|
// src/tags/try.ts
|
|
1185
1265
|
async function executeTry(session, element) {
|
|
1186
1266
|
setExceptionBoundary(session);
|
|
1187
|
-
const { integrateChildren: integrateChildren2 } = await import("./interpreter-
|
|
1267
|
+
const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
|
|
1188
1268
|
await integrateChildren2(session, element);
|
|
1189
1269
|
unsetExceptionBoundary(session);
|
|
1190
1270
|
}
|
|
@@ -1194,7 +1274,7 @@ async function executeCatch(session, element) {
|
|
|
1194
1274
|
const exceptionName = element.attributes?.name || "exception";
|
|
1195
1275
|
const caughtCount = lookupException(session, exceptionName);
|
|
1196
1276
|
if (caughtCount > 0) {
|
|
1197
|
-
const { integrateChildren: integrateChildren2 } = await import("./interpreter-
|
|
1277
|
+
const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
|
|
1198
1278
|
await integrateChildren2(session, element);
|
|
1199
1279
|
}
|
|
1200
1280
|
flushCurrentException(session);
|
|
@@ -1203,7 +1283,7 @@ async function executeCatch(session, element) {
|
|
|
1203
1283
|
// src/tags/exception.ts
|
|
1204
1284
|
async function executeException(session, element) {
|
|
1205
1285
|
const exceptions = getCurrentExceptions(session);
|
|
1206
|
-
const { integrateChildren: integrateChildren2 } = await import("./interpreter-
|
|
1286
|
+
const { integrateChildren: integrateChildren2 } = await import("./interpreter-BYQIE2MI.js");
|
|
1207
1287
|
for (const exceptionDom of exceptions) {
|
|
1208
1288
|
await integrateChildren2(session, exceptionDom);
|
|
1209
1289
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
execute
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-3LRJLSZC.js";
|
|
5
|
+
import "./chunk-UDA4H3GU.js";
|
|
6
6
|
import "./chunk-E7IWGUE6.js";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
@@ -11,7 +11,7 @@ import "dotenv/config";
|
|
|
11
11
|
// package.json
|
|
12
12
|
var package_default = {
|
|
13
13
|
name: "dirac-lang",
|
|
14
|
-
version: "0.1.
|
|
14
|
+
version: "0.1.10",
|
|
15
15
|
description: "LLM-Augmented Declarative Execution",
|
|
16
16
|
type: "module",
|
|
17
17
|
main: "dist/index.js",
|
|
@@ -21,8 +21,9 @@ var package_default = {
|
|
|
21
21
|
},
|
|
22
22
|
scripts: {
|
|
23
23
|
dev: "tsx src/cli.ts",
|
|
24
|
-
build: "tsup src/index.ts src/cli.ts --format esm --dts --clean",
|
|
25
|
-
test: "
|
|
24
|
+
build: "tsup src/index.ts src/cli.ts src/test-runner.ts --format esm --dts --clean",
|
|
25
|
+
test: "npm run build && node dist/test-runner.js tests",
|
|
26
|
+
"test:watch": "npm run build && node dist/test-runner.js tests --watch",
|
|
26
27
|
typecheck: "tsc --noEmit"
|
|
27
28
|
},
|
|
28
29
|
keywords: [
|
package/dist/index.js
CHANGED
|
@@ -2,11 +2,11 @@ import {
|
|
|
2
2
|
createLLMAdapter,
|
|
3
3
|
execute,
|
|
4
4
|
executeUserCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-3LRJLSZC.js";
|
|
6
6
|
import {
|
|
7
7
|
DiracParser,
|
|
8
8
|
integrate
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UDA4H3GU.js";
|
|
10
10
|
import {
|
|
11
11
|
createSession,
|
|
12
12
|
getAvailableSubroutines,
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// src/utils/tag-validator.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
var SIMILARITY_CUTOFF = 0.75;
|
|
5
|
+
function getEmbeddingServerConfig() {
|
|
6
|
+
try {
|
|
7
|
+
const config = yaml.load(fs.readFileSync("config.yml", "utf8"));
|
|
8
|
+
const host = config.embeddingServer?.host || "localhost";
|
|
9
|
+
const port = config.embeddingServer?.port || 11435;
|
|
10
|
+
return { host, port };
|
|
11
|
+
} catch {
|
|
12
|
+
return { host: "localhost", port: 11435 };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function getEmbeddings(tags) {
|
|
16
|
+
const { host, port } = getEmbeddingServerConfig();
|
|
17
|
+
return await Promise.all(tags.map(async (tag) => {
|
|
18
|
+
const response = await fetch(`http://${host}:${port}/api/embeddings`, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: { "Content-Type": "application/json" },
|
|
21
|
+
body: JSON.stringify({ model: "embeddinggemma", prompt: tag })
|
|
22
|
+
});
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
return data.embedding;
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
function cosine(a, b) {
|
|
28
|
+
const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);
|
|
29
|
+
const normA = Math.sqrt(a.reduce((sum, v) => sum + v * v, 0));
|
|
30
|
+
const normB = Math.sqrt(b.reduce((sum, v) => sum + v * v, 0));
|
|
31
|
+
return dot / (normA * normB);
|
|
32
|
+
}
|
|
33
|
+
async function getBestTagMatch(candidate, allowed) {
|
|
34
|
+
const tags = [candidate, ...allowed];
|
|
35
|
+
const embeddings = await getEmbeddings(tags);
|
|
36
|
+
const candidateVec = embeddings[0];
|
|
37
|
+
const allowedVecs = embeddings.slice(1);
|
|
38
|
+
let bestIdx = 0, bestScore = -1;
|
|
39
|
+
allowedVecs.forEach((vec, i) => {
|
|
40
|
+
const score = cosine(candidateVec, vec);
|
|
41
|
+
if (score > bestScore) {
|
|
42
|
+
bestScore = score;
|
|
43
|
+
bestIdx = i;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return { tag: allowed[bestIdx], score: bestScore };
|
|
47
|
+
}
|
|
48
|
+
async function validateTag(session, element, options = {}) {
|
|
49
|
+
const { autocorrect = false, similarityCutoff = SIMILARITY_CUTOFF } = options;
|
|
50
|
+
const { getAvailableSubroutines } = await import("./session-Z37PNJOE.js");
|
|
51
|
+
const subroutines = getAvailableSubroutines(session);
|
|
52
|
+
const allowed = new Set(subroutines.map((s) => s.name));
|
|
53
|
+
const tagName = element.tag;
|
|
54
|
+
const result = {
|
|
55
|
+
valid: false,
|
|
56
|
+
tagName,
|
|
57
|
+
originalTag: tagName,
|
|
58
|
+
corrected: false,
|
|
59
|
+
errors: [],
|
|
60
|
+
warnings: []
|
|
61
|
+
};
|
|
62
|
+
if (allowed.has(tagName)) {
|
|
63
|
+
const sub = subroutines.find((s) => s.name === tagName);
|
|
64
|
+
if (sub && Array.isArray(sub.parameters)) {
|
|
65
|
+
const paramNames = sub.parameters.map((p) => p.name);
|
|
66
|
+
for (const param of sub.parameters) {
|
|
67
|
+
if (param.required && !(param.name in element.attributes)) {
|
|
68
|
+
result.errors.push(`Missing required parameter: ${param.name}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
for (const attr in element.attributes) {
|
|
72
|
+
if (!paramNames.includes(attr)) {
|
|
73
|
+
result.warnings.push(`Unknown attribute: ${attr}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
result.valid = result.errors.length === 0;
|
|
78
|
+
} else {
|
|
79
|
+
const best = await getBestTagMatch(tagName, Array.from(allowed));
|
|
80
|
+
if (best.score >= similarityCutoff) {
|
|
81
|
+
result.similarity = best.score;
|
|
82
|
+
if (autocorrect) {
|
|
83
|
+
result.tagName = best.tag;
|
|
84
|
+
result.corrected = true;
|
|
85
|
+
result.warnings.push(`Auto-corrected from <${tagName}> to <${best.tag}> (similarity: ${best.score.toFixed(2)})`);
|
|
86
|
+
const sub = subroutines.find((s) => s.name === best.tag);
|
|
87
|
+
if (sub && Array.isArray(sub.parameters)) {
|
|
88
|
+
const paramNames = sub.parameters.map((p) => p.name);
|
|
89
|
+
for (const param of sub.parameters) {
|
|
90
|
+
if (param.required && !(param.name in element.attributes)) {
|
|
91
|
+
result.errors.push(`Missing required parameter: ${param.name}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const attr in element.attributes) {
|
|
95
|
+
if (!paramNames.includes(attr)) {
|
|
96
|
+
result.warnings.push(`Unknown attribute: ${attr}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
result.valid = result.errors.length === 0;
|
|
101
|
+
} else {
|
|
102
|
+
result.errors.push(`Tag <${tagName}> does not exist. Did you mean <${best.tag}>? (similarity: ${best.score.toFixed(2)})`);
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
result.errors.push(`Tag <${tagName}> does not exist and no similar tag was found.`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
async function validateDiracCode(session, ast, options = {}) {
|
|
111
|
+
const results = [];
|
|
112
|
+
const errorMessages = [];
|
|
113
|
+
async function validateElement(element) {
|
|
114
|
+
if (element.tag && element.tag !== "dirac" && element.tag !== "") {
|
|
115
|
+
const result = await validateTag(session, element, options);
|
|
116
|
+
results.push(result);
|
|
117
|
+
if (!result.valid) {
|
|
118
|
+
errorMessages.push(`<${result.originalTag}>: ${result.errors.join(", ")}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const child of element.children) {
|
|
122
|
+
await validateElement(child);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await validateElement(ast);
|
|
126
|
+
return {
|
|
127
|
+
valid: errorMessages.length === 0,
|
|
128
|
+
results,
|
|
129
|
+
errorMessages
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function applyCorrectedTags(ast, results) {
|
|
133
|
+
let resultIndex = 0;
|
|
134
|
+
function correctElement(element) {
|
|
135
|
+
if (element.tag && element.tag !== "dirac" && element.tag !== "") {
|
|
136
|
+
const result = results[resultIndex++];
|
|
137
|
+
if (result && result.corrected) {
|
|
138
|
+
element = { ...element, tag: result.tagName };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...element,
|
|
143
|
+
children: element.children.map((child) => correctElement(child))
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return correctElement(ast);
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
applyCorrectedTags,
|
|
150
|
+
validateDiracCode,
|
|
151
|
+
validateTag
|
|
152
|
+
};
|