@rong/agentscript 0.1.0 → 0.1.2
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/INSTALL.md +4 -4
- package/README.md +100 -62
- package/dist/parser/parser.js +1 -4
- package/dist/parser/tokenizer.js +2 -1
- package/dist/providers/llm/anthropic.js +2 -1
- package/dist/providers/llm/ollama.js +5 -2
- package/dist/providers/llm/openai.js +7 -4
- package/dist/providers/mock/index.js +1 -1
- package/dist/runtime/context.js +5 -3
- package/dist/runtime/generate.js +4 -2
- package/dist/runtime/interpreter.js +7 -7
- package/dist/semantic/analyzer.js +3 -1
- package/docs/cn/context-engineering.md +22 -34
- package/docs/cn/final-expression-return.md +197 -0
- package/docs/cn/language.md +21 -28
- package/docs/design-history/v0-design.md +21 -31
- package/docs/design-history/v0-implement.md +3 -3
- package/docs/design-history/v1-design.md +5 -9
- package/docs/design-history/v2-design.md +13 -19
- package/docs/en/context-engineering.md +19 -29
- package/docs/en/final-expression-return.md +197 -0
- package/docs/en/language.md +21 -28
- package/examples/changelog.as +6 -8
- package/examples/extract.as +5 -7
- package/examples/review.as +5 -7
- package/examples/summarize.as +5 -7
- package/examples/translate.as +5 -7
- package/package.json +10 -2
- package/tutorials/cli.as +3 -5
- package/tutorials/helloworld.as +1 -1
- package/tutorials/memory.as +1 -1
- package/tutorials/plan-execute.as +17 -25
- package/tutorials/react.as +15 -23
- package/tutorials/repl.as +1 -1
- package/tutorials/self-improve.as +8 -12
package/INSTALL.md
CHANGED
|
@@ -4,18 +4,18 @@ AgentScript is distributed as an npm package and can also be run from source.
|
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
7
|
-
- Node.js
|
|
7
|
+
- Node.js >= 22.5.
|
|
8
8
|
- npm.
|
|
9
9
|
- Optional: Ollama, OpenAI, or Anthropic credentials when running with `--real-llm`.
|
|
10
10
|
|
|
11
|
-
The
|
|
11
|
+
The SQLite memory backend uses Node's built-in `node:sqlite` module, so Node.js 22.5 or newer is required.
|
|
12
12
|
|
|
13
13
|
## Install from npm
|
|
14
14
|
|
|
15
15
|
After the package is published:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install -g agentscript
|
|
18
|
+
npm install -g @rong/agentscript
|
|
19
19
|
agentscript examples/review.as --input '{"path":"src"}'
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -24,7 +24,7 @@ agentscript examples/review.as --input '{"path":"src"}'
|
|
|
24
24
|
After the package is published:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npx agentscript examples/review.as --input '{"path":"src"}'
|
|
27
|
+
npx @rong/agentscript examples/review.as --input '{"path":"src"}'
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## Run from source
|
package/README.md
CHANGED
|
@@ -1,69 +1,40 @@
|
|
|
1
1
|
# AgentScript
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
> `use` declares what the model
|
|
3
|
+
> **Agent context as code.**
|
|
4
|
+
> `use` declares what the model can see.
|
|
5
|
+
> `generate` defines the only LLM call site and optional output shape.
|
|
5
6
|
> Zero runtime dependencies. TypeScript-powered.
|
|
6
7
|
|
|
7
8
|
```agentscript
|
|
8
9
|
use scratch.summary < 2k
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
generate({ input: "Answer from observations" }) -> {
|
|
11
|
+
ok boolean
|
|
12
|
+
text string
|
|
11
13
|
}
|
|
12
14
|
```
|
|
13
15
|
|
|
14
|
-
[](LICENSE)
|
|
15
17
|

|
|
16
|
-

|
|
17
19
|
|
|
18
20
|
[中文版](./README-CN.md)
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
After building agents with Python and TypeScript, the author kept running into the same problem: prompt context management. What data actually reaches the LLM? Where does one agent's context end and another's begin? How do you audit what the model saw?
|
|
23
|
-
|
|
24
|
-
AgentScript was designed to solve this — not as a general-purpose language, nor a declarative config, nor a prompt template, but as a **DSL** that mixes imperative control flow with explicit, scope-governed context declarations.
|
|
25
|
-
|
|
26
|
-
It gives you two things that general-purpose languages don't: a first-class `use` keyword that declares *which* data enters the LLM prompt, and a first-class `generate` expression that defines *what* the LLM must return. Everything else — variables, functions, agents, imports, loops — exists to support this core workflow. Scopes enforce context boundaries naturally: what's `use`d in one function stays there; child scopes inherit but never leak upward.
|
|
27
|
-
|
|
28
|
-
The result is a language purpose-built for composing agent patterns — ReAct, Plan-and-Execute, Reflection, Multi-Agent — where prompt context is always visible, auditable, and under your control.
|
|
29
|
-
|
|
30
|
-
## How it works
|
|
22
|
+
## Install
|
|
31
23
|
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
A[".as source"] --> B["Parser"]
|
|
35
|
-
B --> C["AST"]
|
|
36
|
-
C --> D["Semantic Analyzer"]
|
|
37
|
-
D --> E["Runtime"]
|
|
38
|
-
E --> F["LLM Provider<br/>(OpenAI / Anthropic / Ollama)"]
|
|
39
|
-
E --> G["Tools<br/>(Find / Grep / File / HTTP / ...)"]
|
|
40
|
-
E --> H["Memory<br/>(JSONL / SQLite)"]
|
|
41
|
-
E --> I["Trace Output"]
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @rong/agentscript
|
|
42
26
|
```
|
|
43
27
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
AgentScript doesn't hardcode agent patterns as keywords. You compose them from the same primitives:
|
|
47
|
-
|
|
48
|
-
| Pattern | Tutorial | What it demonstrates |
|
|
49
|
-
|---------|----------|---------------------|
|
|
50
|
-
| **ReAct** | `tutorials/react.as` | Reason → Act → Observe loop with explicit context |
|
|
51
|
-
| **Plan-and-Execute** | `tutorials/plan-execute.as` | Generate plan, execute steps, verify, re-plan on failure |
|
|
52
|
-
| **Reflection / Self-Improvement** | `tutorials/self-improve.as` | Query past lessons → generate → reflect → persist new lessons |
|
|
53
|
-
| **Multi-Agent** | `tutorials/plan-execute.as` | Independent agents with isolated context boundaries |
|
|
54
|
-
|
|
55
|
-
Every pattern is explicit — which data enters the prompt, which tools each agent can use, and which output shape each LLM call must satisfy.
|
|
56
|
-
|
|
57
|
-
## Install
|
|
28
|
+
Then run the CLI:
|
|
58
29
|
|
|
59
30
|
```bash
|
|
60
|
-
|
|
31
|
+
agentscript --help
|
|
61
32
|
```
|
|
62
33
|
|
|
63
34
|
Or run without installing:
|
|
64
35
|
|
|
65
36
|
```bash
|
|
66
|
-
npx agentscript examples/review.as --input '{"path":"src"}'
|
|
37
|
+
npx @rong/agentscript examples/review.as --input '{"path":"src"}'
|
|
67
38
|
```
|
|
68
39
|
|
|
69
40
|
## Quick start
|
|
@@ -93,16 +64,14 @@ main agent FileSummarizer {
|
|
|
93
64
|
use input.path
|
|
94
65
|
use content < 8k
|
|
95
66
|
|
|
96
|
-
|
|
67
|
+
generate({
|
|
97
68
|
input: "Summarize the file for a busy teammate"
|
|
98
69
|
limit: 1000
|
|
99
|
-
}) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
action_items list[string]
|
|
105
|
-
}
|
|
70
|
+
}) -> {
|
|
71
|
+
title string
|
|
72
|
+
summary string
|
|
73
|
+
key_points list[string]
|
|
74
|
+
action_items list[string]
|
|
106
75
|
}
|
|
107
76
|
}
|
|
108
77
|
}
|
|
@@ -124,6 +93,76 @@ Expected output (with mock LLM):
|
|
|
124
93
|
|
|
125
94
|
With `--real-llm`, the fields are populated by the model.
|
|
126
95
|
|
|
96
|
+
The optional block after `generate` is an output schema, not ordinary object construction.
|
|
97
|
+
|
|
98
|
+
## What problem it solves
|
|
99
|
+
|
|
100
|
+
LLMs are stateless by nature. Each call is a fresh start. To give an agent continuity of thought, every input must be carefully assembled — what researchers and practitioners call context engineering.
|
|
101
|
+
|
|
102
|
+
After building agents with Python and TypeScript, the author kept running into the same problem: prompt context management. What data actually reaches the LLM? Where does one agent's context end and another's begin? How do you audit what the model saw?
|
|
103
|
+
|
|
104
|
+
## What makes AgentScript different?
|
|
105
|
+
|
|
106
|
+
AgentScript is not:
|
|
107
|
+
|
|
108
|
+
- a prompt template
|
|
109
|
+
- a YAML config format
|
|
110
|
+
- a general-purpose agent framework
|
|
111
|
+
|
|
112
|
+
It is a small language for one thing:
|
|
113
|
+
|
|
114
|
+
> making LLM prompt context explicit, scoped, typed, traceable, and compilable.
|
|
115
|
+
|
|
116
|
+
It gives you two things that general-purpose languages don't: a first-class `use` keyword that declares *which* data enters the LLM prompt, and a first-class `generate` expression that defines an LLM call with an optional output contract. Everything else — variables, functions, agents, imports, loops — exists to support this core workflow. Scopes enforce context boundaries naturally: what's `use`d in one function stays there; child scopes inherit but never leak upward. Functions can also return their final top-level expression directly, which keeps typical LLM workflows concise.
|
|
117
|
+
|
|
118
|
+
## How it works
|
|
119
|
+
|
|
120
|
+
```mermaid
|
|
121
|
+
graph LR
|
|
122
|
+
A[".as source"] --> B["Parser"]
|
|
123
|
+
B --> C["AST"]
|
|
124
|
+
C --> D["Semantic Analyzer"]
|
|
125
|
+
D --> E["Runtime"]
|
|
126
|
+
E --> F["LLM Provider<br/>(OpenAI / Anthropic / Ollama)"]
|
|
127
|
+
E --> G["Tools<br/>(Find / Grep / File / HTTP / ...)"]
|
|
128
|
+
E --> H["Memory<br/>(JSONL / SQLite)"]
|
|
129
|
+
E --> I["Trace Output"]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Status
|
|
133
|
+
|
|
134
|
+
AgentScript is experimental.
|
|
135
|
+
|
|
136
|
+
Currently implemented:
|
|
137
|
+
|
|
138
|
+
- parser
|
|
139
|
+
- semantic checker
|
|
140
|
+
- mock runtime
|
|
141
|
+
- OpenAI / Anthropic / Ollama LLM adapters
|
|
142
|
+
- file and environment tools
|
|
143
|
+
- JSONL and SQLite memory backends
|
|
144
|
+
- trace output
|
|
145
|
+
|
|
146
|
+
Planned:
|
|
147
|
+
|
|
148
|
+
- stable IR
|
|
149
|
+
- richer diagnostics
|
|
150
|
+
- VS Code syntax support
|
|
151
|
+
- package publishing hardening
|
|
152
|
+
|
|
153
|
+
## Agent patterns as composable primitives
|
|
154
|
+
|
|
155
|
+
AgentScript doesn't hardcode agent patterns as keywords. You compose them from the same primitives:
|
|
156
|
+
|
|
157
|
+
| Pattern | Tutorial | What it demonstrates |
|
|
158
|
+
|---------|----------|---------------------|
|
|
159
|
+
| **ReAct** | `tutorials/react.as` | Reason → Act → Observe loop with explicit context |
|
|
160
|
+
| **Plan-and-Execute** | `tutorials/plan-execute.as` | Generate plan, execute steps, verify, re-plan on failure |
|
|
161
|
+
| **Reflection / Self-Improvement** | `tutorials/self-improve.as` | Query past lessons → generate → reflect → persist new lessons |
|
|
162
|
+
| **Multi-Agent** | `tutorials/plan-execute.as` | Independent agents with isolated context boundaries |
|
|
163
|
+
|
|
164
|
+
Every pattern is explicit — which data enters the prompt, which tools each agent can use, and which output shape each LLM call must satisfy when one is declared.
|
|
165
|
+
|
|
127
166
|
## Language at a glance
|
|
128
167
|
|
|
129
168
|
```agentscript
|
|
@@ -152,18 +191,16 @@ main agent ResearchAgent {
|
|
|
152
191
|
done = enough(input.question, scratch)
|
|
153
192
|
}
|
|
154
193
|
|
|
155
|
-
|
|
194
|
+
answer(input.question, scratch)
|
|
156
195
|
}
|
|
157
196
|
|
|
158
197
|
func answer(question, scratch) {
|
|
159
198
|
use question
|
|
160
199
|
use scratch.summary < 2k
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
error string
|
|
166
|
-
}
|
|
200
|
+
generate({ input: "Answer using only the observations" }) -> {
|
|
201
|
+
ok boolean
|
|
202
|
+
text string
|
|
203
|
+
error string
|
|
167
204
|
}
|
|
168
205
|
}
|
|
169
206
|
}
|
|
@@ -172,10 +209,11 @@ main agent ResearchAgent {
|
|
|
172
209
|
## Key ideas
|
|
173
210
|
|
|
174
211
|
1. **`use` is explicit context** — nothing enters the LLM prompt unless `use`d
|
|
175
|
-
2. **`generate` is the only LLM call site** — with a required input instruction and
|
|
176
|
-
3. **
|
|
177
|
-
4. **
|
|
178
|
-
5. **
|
|
212
|
+
2. **`generate` is the only LLM call site** — with a required input instruction and optional output shape
|
|
213
|
+
3. **Final expression return keeps flows concise** — a function returns its final top-level expression
|
|
214
|
+
4. **Scope is context boundary** — functions, agents, and blocks isolate prompt visibility
|
|
215
|
+
5. **Tools, memory, and files are imported resources** — with auditable access
|
|
216
|
+
6. **Trace is built in** — every `generate` and `use` is recorded for debugging
|
|
179
217
|
|
|
180
218
|
## Why not just Python or TypeScript?
|
|
181
219
|
|
package/dist/parser/parser.js
CHANGED
|
@@ -408,10 +408,7 @@ class Parser {
|
|
|
408
408
|
this.consume("(");
|
|
409
409
|
const options = this.parseGenerateOptions();
|
|
410
410
|
this.consume(")");
|
|
411
|
-
this.
|
|
412
|
-
this.consume("return");
|
|
413
|
-
const returnShape = this.parseShapeObject();
|
|
414
|
-
this.consume("}");
|
|
411
|
+
const returnShape = this.match("->") ? this.parseShapeObject() : undefined;
|
|
415
412
|
return {
|
|
416
413
|
kind: "GenerateExpr",
|
|
417
414
|
options,
|
package/dist/parser/tokenizer.js
CHANGED
|
@@ -40,6 +40,7 @@ const SYMBOLS = new Set([
|
|
|
40
40
|
"=",
|
|
41
41
|
"!",
|
|
42
42
|
"<",
|
|
43
|
+
"-",
|
|
43
44
|
"*"
|
|
44
45
|
]);
|
|
45
46
|
export function tokenize(source) {
|
|
@@ -77,7 +78,7 @@ class Scanner {
|
|
|
77
78
|
}
|
|
78
79
|
if (SYMBOLS.has(char)) {
|
|
79
80
|
const twoChar = `${char}${this.peekNext()}`;
|
|
80
|
-
if (twoChar === "==" || twoChar === "!=") {
|
|
81
|
+
if (twoChar === "==" || twoChar === "!=" || twoChar === "->") {
|
|
81
82
|
this.advance();
|
|
82
83
|
this.advance();
|
|
83
84
|
tokens.push({
|
|
@@ -19,7 +19,8 @@ export async function callAnthropic(request, parsed, options, fetchImpl, timeout
|
|
|
19
19
|
"x-api-key": apiKey,
|
|
20
20
|
"anthropic-version": "2023-06-01",
|
|
21
21
|
});
|
|
22
|
-
|
|
22
|
+
const text = readAnthropicText(response);
|
|
23
|
+
return request.returnShape ? parseJsonText(text) : text;
|
|
23
24
|
}
|
|
24
25
|
function readAnthropicText(value) {
|
|
25
26
|
if (!value || typeof value !== "object" || Array.isArray(value) || !Array.isArray(value.content)) {
|
|
@@ -4,16 +4,19 @@ export async function callOllama(request, parsed, fetchImpl, timeoutMs, baseUrl)
|
|
|
4
4
|
model: parsed.model,
|
|
5
5
|
stream: false,
|
|
6
6
|
think: false,
|
|
7
|
-
format: request.builtContext.returnSchema,
|
|
8
7
|
messages: [
|
|
9
8
|
{ role: "system", content: request.builtContext.system },
|
|
10
9
|
{ role: "user", content: request.builtContext.finalUserMessage },
|
|
11
10
|
],
|
|
12
11
|
};
|
|
12
|
+
if (request.builtContext.returnSchema) {
|
|
13
|
+
body.format = request.builtContext.returnSchema;
|
|
14
|
+
}
|
|
13
15
|
const maxTokens = budgetToTokenLimit(request);
|
|
14
16
|
if (maxTokens) {
|
|
15
17
|
body.options = { num_predict: maxTokens };
|
|
16
18
|
}
|
|
17
19
|
const response = await postJson(fetchImpl, `${baseUrl}/api/chat`, body, timeoutMs);
|
|
18
|
-
|
|
20
|
+
const text = readPath(response, ["message", "content"]);
|
|
21
|
+
return request.returnShape ? parseJsonText(text) : text;
|
|
19
22
|
}
|
|
@@ -11,15 +11,17 @@ export async function callOpenAI(request, parsed, options, fetchImpl, timeoutMs,
|
|
|
11
11
|
{ role: "system", content: request.builtContext.system },
|
|
12
12
|
{ role: "user", content: request.builtContext.finalUserMessage },
|
|
13
13
|
],
|
|
14
|
-
|
|
14
|
+
};
|
|
15
|
+
if (request.builtContext.returnSchema) {
|
|
16
|
+
body.response_format = {
|
|
15
17
|
type: "json_schema",
|
|
16
18
|
json_schema: {
|
|
17
19
|
name: "agentscript_generate",
|
|
18
20
|
strict: true,
|
|
19
21
|
schema: request.builtContext.returnSchema,
|
|
20
22
|
},
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
23
25
|
const maxTokens = budgetToTokenLimit(request);
|
|
24
26
|
if (maxTokens) {
|
|
25
27
|
body.max_completion_tokens = maxTokens;
|
|
@@ -27,5 +29,6 @@ export async function callOpenAI(request, parsed, options, fetchImpl, timeoutMs,
|
|
|
27
29
|
const response = await postJson(fetchImpl, `${baseUrl}/chat/completions`, body, timeoutMs, {
|
|
28
30
|
authorization: `Bearer ${apiKey}`,
|
|
29
31
|
});
|
|
30
|
-
|
|
32
|
+
const text = readPath(response, ["choices", 0, "message", "content"]);
|
|
33
|
+
return request.returnShape ? parseJsonText(text) : text;
|
|
31
34
|
}
|
|
@@ -2,7 +2,7 @@ import { sanitizeForJson } from "../../runtime/json.js";
|
|
|
2
2
|
import { buildValueFromShape } from "../../runtime/shape.js";
|
|
3
3
|
export class MockLlmProvider {
|
|
4
4
|
async generate(request) {
|
|
5
|
-
return buildValueFromShape(request.returnShape);
|
|
5
|
+
return request.returnShape ? buildValueFromShape(request.returnShape) : null;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
export class MockToolProvider {
|
package/dist/runtime/context.js
CHANGED
|
@@ -4,7 +4,7 @@ export function buildContext(input) {
|
|
|
4
4
|
const instruction = sanitizeForJson(input.instruction);
|
|
5
5
|
const instructionText = renderJson(instruction);
|
|
6
6
|
const system = buildSystemPrompt(input.agentName, input.identity);
|
|
7
|
-
const returnSchema = shapeToSchema(input.returnShape);
|
|
7
|
+
const returnSchema = input.returnShape ? shapeToSchema(input.returnShape) : undefined;
|
|
8
8
|
return {
|
|
9
9
|
agentName: input.agentName,
|
|
10
10
|
model: input.model,
|
|
@@ -76,7 +76,9 @@ function buildFinalUserMessage(context, instructionText, returnSchema) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
sections.push("Instruction:", instructionText);
|
|
79
|
-
|
|
79
|
+
if (returnSchema) {
|
|
80
|
+
sections.push("Return JSON matching this schema:", renderJson(returnSchema));
|
|
81
|
+
}
|
|
80
82
|
return sections.join("\n");
|
|
81
83
|
}
|
|
82
84
|
function renderJson(value) {
|
|
@@ -153,7 +155,7 @@ export function builtContextToJson(context) {
|
|
|
153
155
|
clippedSize: item.clippedSize,
|
|
154
156
|
})),
|
|
155
157
|
instruction: context.instruction,
|
|
156
|
-
returnSchema: context.returnSchema,
|
|
158
|
+
returnSchema: context.returnSchema ?? null,
|
|
157
159
|
budget: budgetToJson(context.budget),
|
|
158
160
|
finalUserMessage: context.finalUserMessage,
|
|
159
161
|
};
|
package/dist/runtime/generate.js
CHANGED
|
@@ -58,8 +58,10 @@ export class GenerateRuntime {
|
|
|
58
58
|
continue;
|
|
59
59
|
}
|
|
60
60
|
try {
|
|
61
|
-
const result = coerceValueToShape(rawResult, expr.returnShape);
|
|
62
|
-
|
|
61
|
+
const result = expr.returnShape ? coerceValueToShape(rawResult, expr.returnShape) : rawResult;
|
|
62
|
+
if (expr.returnShape) {
|
|
63
|
+
validateValueAgainstShape(result, expr.returnShape, expr.range);
|
|
64
|
+
}
|
|
63
65
|
this.trace.push({
|
|
64
66
|
kind: "generate",
|
|
65
67
|
data: {
|
|
@@ -151,11 +151,8 @@ class Interpreter {
|
|
|
151
151
|
this.callDepth += 1;
|
|
152
152
|
const scope = await this.buildFunctionScope(agent, fn, args);
|
|
153
153
|
try {
|
|
154
|
-
const signal = await this.executeBlock(fn.body, scope);
|
|
155
|
-
|
|
156
|
-
throw new RuntimeError(`Function '${agent.name}.${name}' completed without return`, fn.range);
|
|
157
|
-
}
|
|
158
|
-
return signal.value;
|
|
154
|
+
const signal = await this.executeBlock(fn.body, scope, true);
|
|
155
|
+
return signal?.value ?? null;
|
|
159
156
|
}
|
|
160
157
|
finally {
|
|
161
158
|
this.callDepth -= 1;
|
|
@@ -194,8 +191,11 @@ class Interpreter {
|
|
|
194
191
|
findFunction(agent, name) {
|
|
195
192
|
return agent.functions.find((fn) => fn.name === name);
|
|
196
193
|
}
|
|
197
|
-
async executeBlock(statements, scope) {
|
|
198
|
-
for (const stmt of statements) {
|
|
194
|
+
async executeBlock(statements, scope, allowFinalExpressionReturn = false) {
|
|
195
|
+
for (const [index, stmt] of statements.entries()) {
|
|
196
|
+
if (allowFinalExpressionReturn && index === statements.length - 1 && stmt.kind === "ExprStmt") {
|
|
197
|
+
return { kind: "return", value: await this.evaluator.evaluate(stmt.expr, scope) };
|
|
198
|
+
}
|
|
199
199
|
const result = await this.executeStatement(stmt, scope);
|
|
200
200
|
if (result)
|
|
201
201
|
return result;
|
|
@@ -366,7 +366,9 @@ class Analyzer {
|
|
|
366
366
|
this.checkExpression(expr.options.input, scope);
|
|
367
367
|
}
|
|
368
368
|
this.checkGenerateInput(expr);
|
|
369
|
-
|
|
369
|
+
if (expr.returnShape) {
|
|
370
|
+
this.checkShapeObject(expr.returnShape);
|
|
371
|
+
}
|
|
370
372
|
this.checkGenerateConfig("model", expr, scope);
|
|
371
373
|
this.checkGenerateConfig("role", expr, scope);
|
|
372
374
|
this.checkGenerateConfig("description", expr, scope);
|
|
@@ -10,13 +10,13 @@ AgentScript 表面上有变量、函数、循环和 Agent 调用,但它的主
|
|
|
10
10
|
|
|
11
11
|
AgentScript 的控制流服务于 prompt context 构建。
|
|
12
12
|
|
|
13
|
-
普通语句负责组织数据、调用工具、调用 Agent 和更新中间状态。LLM 调用只能通过 `generate(...) {
|
|
13
|
+
普通语句负责组织数据、调用工具、调用 Agent 和更新中间状态。LLM 调用只能通过 `generate(...) -> { ... }` 发生,而 `generate` 能看到的上下文必须由 `use` 显式声明。
|
|
14
14
|
|
|
15
15
|
AgentScript 的核心对象是:
|
|
16
16
|
|
|
17
17
|
- `Data`:普通变量、JSON、list、file import、tool observation 和 Agent 返回值。
|
|
18
18
|
- `Context Source`:由 `use expr < budget` 声明的 prompt context 来源。
|
|
19
|
-
- `Generation Site`:由 `generate({ input, limit, attempts, debug })
|
|
19
|
+
- `Generation Site`:由 `generate({ input, limit, attempts, debug }) -> shape` 声明的一次 LLM 调用。
|
|
20
20
|
- `Boundary`:由 Agent、function 和 block scope 形成的 context 可见性边界。
|
|
21
21
|
|
|
22
22
|
## `use` 的语义
|
|
@@ -32,15 +32,13 @@ func answer(question, scratch) {
|
|
|
32
32
|
use question
|
|
33
33
|
use scratch.summary < 2k
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
generate({
|
|
36
36
|
input: "Answer the question using collected facts"
|
|
37
37
|
limit: 800
|
|
38
|
-
}) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
error string
|
|
43
|
-
}
|
|
38
|
+
}) -> {
|
|
39
|
+
ok boolean
|
|
40
|
+
text string
|
|
41
|
+
error string
|
|
44
42
|
}
|
|
45
43
|
}
|
|
46
44
|
```
|
|
@@ -63,10 +61,8 @@ main func(input) {
|
|
|
63
61
|
scratch.add({ fact: "A" })
|
|
64
62
|
scratch.add({ fact: "B" })
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
text string
|
|
69
|
-
}
|
|
64
|
+
generate({ input: "Answer from scratch" }) -> {
|
|
65
|
+
text string
|
|
70
66
|
}
|
|
71
67
|
}
|
|
72
68
|
```
|
|
@@ -123,15 +119,13 @@ AgentScript 中的作用域不仅是变量可见性规则,也是 prompt contex
|
|
|
123
119
|
```agentscript
|
|
124
120
|
func caller(input) {
|
|
125
121
|
use input.goal
|
|
126
|
-
|
|
122
|
+
helper(input)
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
func helper(input) {
|
|
130
126
|
use input.detail
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
ok boolean
|
|
134
|
-
}
|
|
127
|
+
generate({ input: "Work on detail" }) -> {
|
|
128
|
+
ok boolean
|
|
135
129
|
}
|
|
136
130
|
}
|
|
137
131
|
```
|
|
@@ -171,10 +165,8 @@ result = Worker({
|
|
|
171
165
|
if condition {
|
|
172
166
|
temp = compute(input)
|
|
173
167
|
use temp
|
|
174
|
-
result = generate({ input: "Use temp" }) {
|
|
175
|
-
|
|
176
|
-
ok boolean
|
|
177
|
-
}
|
|
168
|
+
result = generate({ input: "Use temp" }) -> {
|
|
169
|
+
ok boolean
|
|
178
170
|
}
|
|
179
171
|
}
|
|
180
172
|
```
|
|
@@ -231,11 +223,9 @@ source label 有助于模型理解 context,也有助于人类审计。
|
|
|
231
223
|
Instruction 层来自 `generate(...)` 参数对象中的 `input` 字段。
|
|
232
224
|
|
|
233
225
|
```agentscript
|
|
234
|
-
generate({ input: "Answer the question using only collected facts" }) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
text string
|
|
238
|
-
}
|
|
226
|
+
generate({ input: "Answer the question using only collected facts" }) -> {
|
|
227
|
+
ok boolean
|
|
228
|
+
text string
|
|
239
229
|
}
|
|
240
230
|
```
|
|
241
231
|
|
|
@@ -245,10 +235,10 @@ Instruction 是本次 LLM 调用的局部任务,不应混入长期 context。
|
|
|
245
235
|
|
|
246
236
|
### Output contract
|
|
247
237
|
|
|
248
|
-
Output contract 来自 `
|
|
238
|
+
Output contract 来自 `generate` 表达式上可选的 `-> { ... }` shape。
|
|
249
239
|
|
|
250
240
|
```agentscript
|
|
251
|
-
|
|
241
|
+
generate({ input: "Answer" }) -> {
|
|
252
242
|
ok boolean
|
|
253
243
|
text string
|
|
254
244
|
error string
|
|
@@ -270,13 +260,11 @@ use scratch.summary < 2k
|
|
|
270
260
|
`generate({ limit: budget }) { ... }` 是 generation budget。
|
|
271
261
|
|
|
272
262
|
```agentscript
|
|
273
|
-
|
|
263
|
+
generate({
|
|
274
264
|
input: "Summarize"
|
|
275
265
|
limit: 500
|
|
276
|
-
}) {
|
|
277
|
-
|
|
278
|
-
text string
|
|
279
|
-
}
|
|
266
|
+
}) -> {
|
|
267
|
+
text string
|
|
280
268
|
}
|
|
281
269
|
```
|
|
282
270
|
|