@yottagraph-app/aether-instructions 1.1.3 → 1.1.4
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/package.json +1 -1
- package/rules/agents.mdc +123 -2
- package/rules/api.mdc +107 -4
- package/rules/architecture.mdc +13 -5
package/package.json
CHANGED
package/rules/agents.mdc
CHANGED
|
@@ -168,8 +168,9 @@ Once deployed, the agent is reachable through the Portal Gateway:
|
|
|
168
168
|
```
|
|
169
169
|
Chat UI (pages/chat.vue)
|
|
170
170
|
→ POST NUXT_PUBLIC_GATEWAY_URL/api/agents/{tenantId}/{agentId}/query
|
|
171
|
-
→ Portal Gateway proxies to Vertex AI Agent Engine
|
|
172
|
-
→ Agent runs,
|
|
171
|
+
→ Portal Gateway proxies to Vertex AI Agent Engine (streamQuery)
|
|
172
|
+
→ Agent runs (may invoke tools, make multiple LLM calls)
|
|
173
|
+
→ Gateway collects the ADK event stream, extracts final text
|
|
173
174
|
→ Chat UI displays it
|
|
174
175
|
```
|
|
175
176
|
|
|
@@ -177,6 +178,126 @@ The gateway URL and tenant ID come from `broadchurch.yaml` (injected as
|
|
|
177
178
|
`NUXT_PUBLIC_GATEWAY_URL` and `NUXT_PUBLIC_TENANT_ORG_ID`). The chat page
|
|
178
179
|
discovers available agents from the Portal config endpoint.
|
|
179
180
|
|
|
181
|
+
### Gateway Request
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
POST {NUXT_PUBLIC_GATEWAY_URL}/api/agents/{tenantId}/{agentId}/query
|
|
185
|
+
Content-Type: application/json
|
|
186
|
+
|
|
187
|
+
{ "message": "Summarize the latest 8-K filing", "session_id": "optional-session-id" }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Omit `session_id` on the first message — the gateway auto-creates one.
|
|
191
|
+
|
|
192
|
+
### Gateway Response Format
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"output": "The agent's final text response",
|
|
197
|
+
"session_id": "session-abc-123",
|
|
198
|
+
"events": [ /* raw ADK event stream */ ]
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
| Field | Type | Description |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| `output` | `string \| any[]` | Usually the agent's final text. Falls back to the raw `events` array if the gateway couldn't extract text. |
|
|
205
|
+
| `session_id` | `string` | Pass back on subsequent messages to continue the conversation. |
|
|
206
|
+
| `events` | `any[]` | Full ADK event stream. Useful for debugging or building UIs that show intermediate agent steps. |
|
|
207
|
+
|
|
208
|
+
**Important:** `output` is NOT always a string. When the agent's response
|
|
209
|
+
involves complex tool chains, the gateway's server-side extraction may miss
|
|
210
|
+
the text and return the raw events array instead. Always use
|
|
211
|
+
`extractAgentText()` to parse `output` safely rather than treating it as a
|
|
212
|
+
string directly.
|
|
213
|
+
|
|
214
|
+
### ADK Event Stream Format
|
|
215
|
+
|
|
216
|
+
The `events` array contains one object per step the agent took. Each event
|
|
217
|
+
has `content.parts[]` where each part is one of:
|
|
218
|
+
|
|
219
|
+
```json
|
|
220
|
+
{ "text": "The agent's text response..." }
|
|
221
|
+
{ "functionCall": { "name": "search", "args": { "q": "AAPL" } } }
|
|
222
|
+
{ "functionResponse": { "name": "search", "response": { "results": [...] } } }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
A typical stream for an agent that uses a tool:
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
[
|
|
229
|
+
{ "content": { "parts": [{ "functionCall": { "name": "search", "args": {"q": "AAPL 8-K"} } }], "role": "model" } },
|
|
230
|
+
{ "content": { "parts": [{ "functionResponse": { "name": "search", "response": {"results": ["..."]} } }], "role": "tool" } },
|
|
231
|
+
{ "content": { "parts": [{ "text": "Here is the summary of the 8-K filing..." }], "role": "model" } }
|
|
232
|
+
]
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The final text is the last `text` part that isn't in a `functionCall` or
|
|
236
|
+
`functionResponse` event. Events may also arrive as JSON strings rather
|
|
237
|
+
than objects — always handle both.
|
|
238
|
+
|
|
239
|
+
### Parsing Agent Responses (Non-Streaming)
|
|
240
|
+
|
|
241
|
+
For one-shot calls (server routes, background tasks) where streaming isn't
|
|
242
|
+
needed, use the buffered `/query` endpoint with `extractAgentText`:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import { extractAgentText } from '~/composables/useAgentChat';
|
|
246
|
+
|
|
247
|
+
const response = await $fetch(url, { method: 'POST', body: { message } });
|
|
248
|
+
const text = extractAgentText(response.output);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`extractAgentText` handles: plain strings, ADK event stream arrays (with
|
|
252
|
+
JSON-string or object elements), single event objects, and several legacy
|
|
253
|
+
Agent Engine response shapes. It skips `functionCall` / `functionResponse`
|
|
254
|
+
events and returns the agent's final text.
|
|
255
|
+
|
|
256
|
+
### Streaming Responses
|
|
257
|
+
|
|
258
|
+
The gateway also exposes a streaming endpoint that returns Server-Sent
|
|
259
|
+
Events as the agent executes. **The `useAgentChat` composable uses this by
|
|
260
|
+
default** — it tries `/stream` first and falls back to `/query`
|
|
261
|
+
automatically.
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
POST {NUXT_PUBLIC_GATEWAY_URL}/api/agents/{tenantId}/{agentId}/stream
|
|
265
|
+
Content-Type: application/json
|
|
266
|
+
|
|
267
|
+
{ "message": "Summarize the latest 8-K filing", "session_id": "optional" }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
The response is an SSE stream with these event types:
|
|
271
|
+
|
|
272
|
+
| Event | Data Shape | Description |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| `text` | `{ "text": "..." }` | Agent text output (replaces previous text) |
|
|
275
|
+
| `function_call` | `{ "name": "...", "args": {...} }` | Agent is calling a tool |
|
|
276
|
+
| `function_response` | `{ "name": "...", "response": {...} }` | Tool returned a result |
|
|
277
|
+
| `error` | `{ "message": "..." }` | Error during processing |
|
|
278
|
+
| `done` | `{ "session_id": "...", "text": "..." }` | Stream complete with final text |
|
|
279
|
+
|
|
280
|
+
For custom agent UIs that need streaming, import `readSSE`:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { readSSE } from '~/composables/useAgentChat';
|
|
284
|
+
|
|
285
|
+
const res = await fetch(streamUrl, {
|
|
286
|
+
method: 'POST',
|
|
287
|
+
headers: { 'Content-Type': 'application/json' },
|
|
288
|
+
body: JSON.stringify({ message: 'Hello' }),
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
for await (const { event, data } of readSSE(res)) {
|
|
292
|
+
if (event === 'text') console.log('Agent says:', data.text);
|
|
293
|
+
if (event === 'function_call') console.log('Calling:', data.name);
|
|
294
|
+
if (event === 'done') console.log('Session:', data.session_id);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The `done` event always includes the final extracted text, so you don't
|
|
299
|
+
need to track text deltas yourself.
|
|
300
|
+
|
|
180
301
|
## Agent Design Guidelines
|
|
181
302
|
|
|
182
303
|
- Keep agents focused: one agent per domain or task type
|
package/rules/api.mdc
CHANGED
|
@@ -51,6 +51,57 @@ Types are also imported from the client:
|
|
|
51
51
|
import type { NamedEntityReport, GetNEIDResponse } from '@yottagraph-app/elemental-api/client';
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
### Client Method Quick Reference
|
|
55
|
+
|
|
56
|
+
All methods return data directly and throw on non-2xx responses.
|
|
57
|
+
|
|
58
|
+
**Entity search and lookup:**
|
|
59
|
+
|
|
60
|
+
| Method | Signature | Purpose |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `getNEID` | `(params: { entityName, maxResults?, includeNames? })` | Lookup entity by name |
|
|
63
|
+
| `findEntities` | `(body: FindEntitiesBody)` | Expression-based search (see `find.md`) |
|
|
64
|
+
| `getNamedEntityReport` | `(neid: string)` | Entity details (name, aliases, type) |
|
|
65
|
+
| `getEntityDetails` | `(neid: string)` | Alias for entity reports |
|
|
66
|
+
|
|
67
|
+
**Properties and schema:**
|
|
68
|
+
|
|
69
|
+
| Method | Signature | Purpose |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `getSchema` | `()` | All entity types (flavors) and properties (PIDs) |
|
|
72
|
+
| `getPropertyValues` | `(body: { eids: string, pids: string })` | Property values (eids/pids are JSON-stringified arrays!) |
|
|
73
|
+
| `summarizeProperty` | `(pid: number)` | Summary stats for a property |
|
|
74
|
+
|
|
75
|
+
**Relationships and graph:**
|
|
76
|
+
|
|
77
|
+
| Method | Signature | Purpose |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `getLinkedEntities` | `(neid, params?: { entity_type?, link_type? })` | Linked entities (person/org/location only) |
|
|
80
|
+
| `getLinks` | `(sourceNeid, targetNeid, params?)` | Links between two specific entities |
|
|
81
|
+
| `getLinkCounts` | `(sourceNeid, targetNeid)` | Link counts between entities |
|
|
82
|
+
| `getNeighborhood` | `(centerNeid, params?)` | Neighboring entities |
|
|
83
|
+
| `getGraphLayout` | `(centerNeid, params?)` | Graph layout for visualization |
|
|
84
|
+
|
|
85
|
+
**News, events, and sentiment:**
|
|
86
|
+
|
|
87
|
+
| Method | Signature | Purpose |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `getArticle` | `(artid: string)` | Article by ID |
|
|
90
|
+
| `getArticleText` | `(artid: string)` | Article full text |
|
|
91
|
+
| `getEvent` | `(eveid: string)` | Event by ID |
|
|
92
|
+
| `getEventsForEntity` | `(params: { neid, startTime?, endTime? })` | Events involving an entity |
|
|
93
|
+
| `getMentions` | `(params: { neid, startTime?, endTime? })` | Mention codes for entities |
|
|
94
|
+
| `getMentionCounts` | `(params: { neid, ... })` | Bucketed mention counts |
|
|
95
|
+
| `getNamedEntitySentiment` | `(neid: string)` | Sentiment for an entity |
|
|
96
|
+
|
|
97
|
+
**Other:**
|
|
98
|
+
|
|
99
|
+
| Method | Signature | Purpose |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `getHealth` | `()` | Health check |
|
|
102
|
+
| `getStatus` | `()` | Server status and capabilities |
|
|
103
|
+
| `adaMessage` | `(body: AdaMessageBody)` | Ada AI chat |
|
|
104
|
+
|
|
54
105
|
## Discovery-First Pattern
|
|
55
106
|
|
|
56
107
|
The knowledge graph contains many entity types and properties, and new datasets
|
|
@@ -81,17 +132,27 @@ knowledge of what's in the graph.
|
|
|
81
132
|
|
|
82
133
|
## API Gotchas
|
|
83
134
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
135
|
+
### `getSchema()` response is nested — WILL crash if you don't handle it
|
|
136
|
+
|
|
137
|
+
The generated TypeScript types suggest `response.properties` and
|
|
138
|
+
`response.flavors` exist at the top level. **They don't.** The API nests
|
|
139
|
+
them under `response.schema`. This mismatch between types and reality
|
|
140
|
+
causes `Cannot read properties of undefined` every time.
|
|
88
141
|
|
|
89
142
|
```typescript
|
|
143
|
+
// WRONG — will crash at runtime despite TypeScript compiling fine:
|
|
144
|
+
const res = await client.getSchema();
|
|
145
|
+
const props = res.properties; // undefined!
|
|
146
|
+
|
|
147
|
+
// CORRECT — always access through .schema:
|
|
90
148
|
const res = await client.getSchema();
|
|
91
149
|
const properties = res.schema?.properties ?? (res as any).properties ?? [];
|
|
92
150
|
const flavors = res.schema?.flavors ?? (res as any).flavors ?? [];
|
|
93
151
|
```
|
|
94
152
|
|
|
153
|
+
The `(res as any).properties` fallback is there in case the API is ever
|
|
154
|
+
fixed to match the types. Use this pattern every time.
|
|
155
|
+
|
|
95
156
|
> **WARNING -- `getPropertyValues()` takes JSON-stringified arrays**: The `eids`
|
|
96
157
|
> and `pids` parameters must be JSON-encoded strings, NOT native arrays. The
|
|
97
158
|
> TypeScript type is `string`, not `string[]`. Passing a raw array will silently
|
|
@@ -139,6 +200,48 @@ See the **cookbook** rule for a full "Get filings for a company" recipe.
|
|
|
139
200
|
(`POST /elemental/find`). Best for filtered searches (by type, property,
|
|
140
201
|
relationship). See `find.md` for the expression language.
|
|
141
202
|
|
|
203
|
+
## Common Entity Relationships
|
|
204
|
+
|
|
205
|
+
The knowledge graph connects entities through relationship properties
|
|
206
|
+
(`data_nindex` PIDs). These are the most common patterns:
|
|
207
|
+
|
|
208
|
+
| From | PID | To | How to traverse |
|
|
209
|
+
|---|---|---|---|
|
|
210
|
+
| Organization | `filed` | Document (filings) | `getPropertyValues` with the `filed` PID on the org's NEID |
|
|
211
|
+
| Organization | `employs` | Person | `getLinkedEntities` (person is a graph node type) |
|
|
212
|
+
| Person | `employed_by` | Organization | `getLinkedEntities` (organization is a graph node type) |
|
|
213
|
+
| Organization | `headquartered_in` | Location | `getLinkedEntities` (location is a graph node type) |
|
|
214
|
+
| Any entity | `related_to` | Any entity | `getLinkedEntities` for person/org/location; `getPropertyValues` for others |
|
|
215
|
+
|
|
216
|
+
**Key constraint:** `getLinkedEntities` only works for three target types:
|
|
217
|
+
**person**, **organization**, **location**. For documents, filings,
|
|
218
|
+
articles, financial instruments, and events, use `getPropertyValues` with
|
|
219
|
+
the relationship PID. See the cookbook rule (recipe #7) for a full filings
|
|
220
|
+
example.
|
|
221
|
+
|
|
222
|
+
**Traversal pattern for non-graph-node types:**
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// 1. Get the PID for the relationship
|
|
226
|
+
const schema = await client.getSchema();
|
|
227
|
+
const pids = schema.schema?.properties ?? [];
|
|
228
|
+
const filedPid = pids.find((p: any) => p.name === 'filed')?.pid;
|
|
229
|
+
|
|
230
|
+
// 2. Get linked entity IDs via getPropertyValues
|
|
231
|
+
const res = await client.getPropertyValues({
|
|
232
|
+
eids: JSON.stringify([orgNeid]),
|
|
233
|
+
pids: JSON.stringify([filedPid]),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// 3. Pad IDs to 20 chars to form valid NEIDs
|
|
237
|
+
const docNeids = res.values.map((v: any) => String(v.value).padStart(20, '0'));
|
|
238
|
+
|
|
239
|
+
// 4. Get details for each linked entity
|
|
240
|
+
const reports = await Promise.all(
|
|
241
|
+
docNeids.map((neid: string) => client.getNamedEntityReport(neid)),
|
|
242
|
+
);
|
|
243
|
+
```
|
|
244
|
+
|
|
142
245
|
## Error Handling
|
|
143
246
|
|
|
144
247
|
```typescript
|
package/rules/architecture.mdc
CHANGED
|
@@ -133,9 +133,17 @@ Tenant-specific configuration generated during provisioning. Contains GCP projec
|
|
|
133
133
|
|
|
134
134
|
## Built-in Pages
|
|
135
135
|
|
|
136
|
-
These pages ship with the template and can be kept, modified, or removed
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- `pages/
|
|
140
|
-
|
|
136
|
+
These pages ship with the template and can be kept, modified, or removed
|
|
137
|
+
based on the app's needs:
|
|
138
|
+
|
|
139
|
+
- `pages/chat.vue` -- Agent chat UI. Talks to deployed ADK agents through
|
|
140
|
+
the Portal Gateway with streaming support. Keep if the app uses AI agents.
|
|
141
|
+
- `pages/mcp.vue` -- MCP Explorer. Browse and test MCP server tools. Keep
|
|
142
|
+
if the app uses MCP servers.
|
|
143
|
+
- `pages/entity-lookup.vue` -- Entity search tool. Useful for looking up
|
|
144
|
+
NEIDs. Keep or remove based on the app.
|
|
145
|
+
|
|
146
|
+
If any of these pages are missing from your project, your project may have
|
|
147
|
+
been created from an older template version. Copy them from the latest
|
|
148
|
+
aether-dev source or run `/update_instructions` to check for updates.
|
|
141
149
|
|