Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
554 changes: 413 additions & 141 deletions toolbox/mdcode/demo/semantic-model/agent/README.md

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion toolbox/mdcode/demo/semantic-model/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
// The judge is a capability rather than a policy: it can settle a rule stated
// in words, and WHICH rules it is asked are the ones the model names in
// `guards`. So supplying one here says nothing about commerce, the same way
// supplying a database connection does not.
// supplying a database connection does not. The same goes for letting it read:
// what it may read is derived from the model, and which rules make it read is
// decided by how they are worded.

import {FunctionTool, InMemoryRunner, LlmAgent} from '@google/adk';
import {Type} from '@google/genai';

import {ApiContext} from '../../../src/libts/gcp/context';
import {GeminiJudge} from '../../../src/libts/gcp/gemini';
import {callableTools, modelTools} from '../../../src/libts/semantic/runtime/agent_tools';
import {modelJudgeStore} from '../../../src/libts/semantic/runtime/judge_store';
import {createSemanticRuntimes} from '../../../src/libts/semantic/runtime/runtime';
import {closeStore} from '../../../src/libts/semantic/runtime/store';

Expand Down Expand Up @@ -58,10 +61,24 @@ process.env.GOOGLE_CLOUD_LOCATION ??= 'us-central1';
// arguments, under a system instruction of its own, outside the agent's
// conversation. So there is nothing in the transcript for the agent to
// argue with, and no turn in which it can talk the gate round.
//
// It is given the model's own tables to read, which is what lets a rule
// compare the call against a number on record -- the order's total -- that
// the caller never has to state and has every reason to misstate. The
// tables are derived from the model under this profile, so this line adds a
// capability and still names nothing about commerce. Every read is printed,
// because a judge that went and looked did something on the caller's behalf
// that the transcript has to show.
const judgeStore = modelJudgeStore(runtime, {
onRead: sql => console.log(` (judge reads) ${sql.replace(/\s+/g, ' ')}`),
});
if ('error' in judgeStore) throw new Error(judgeStore.error);

const judge = new GeminiJudge(ApiContext.default(), {
project: process.env.GOOGLE_CLOUD_PROJECT,
location: process.env.GOOGLE_CLOUD_LOCATION,
model: process.env.DEMO_JUDGE_MODEL,
store: judgeStore,
});

// 3. Derive what the model offers, and keep what this binding can serve.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
# those are the ones something can answer today. The expressions are declared
# and named by nothing, which makes them inert: a constraint takes effect where
# something references it.
#
# One of the judgments compares the call against a row in the database, which
# a judge can settle only if it can read the database. Running this demo
# therefore takes a judge that has been given one -- `--judge-reads-store` on
# the command line, or a `store` on the judge the agent hires.

version: "0.2.0.dev0/google"

Expand Down Expand Up @@ -116,10 +121,13 @@ semantic_model:
- {concept: LineItem, operation: create, fields: [type, amount, memo]}
- {concept: Order, operation: modify, fields: [total]}
# Which rules this call must pass, and the only place a constraint
# takes effect. All three are judgments, so every gate here costs a
# model call -- the loader says so at load time, and it is the price of
# gating on the half of the policy something can settle today.
# takes effect. All four are judgments, so every gate here costs at
# least one model call -- the loader says so at load time, and it is
# the price of gating on the half of the policy something can settle
# today. The first of them also costs a read, because it is about a
# number that is in the database rather than in the call.
guards:
- CreditWithinOrderTotalWithJudge
- CreditUnderReviewThresholdWithJudge
- CreditMemoNamesAServiceFailure
- CreditIsNotSplitToAvoidReview
Expand All @@ -146,13 +154,15 @@ semantic_model:
description: >-
A credit cannot exceed the total of the order it credits. Lower the
credit amount, or split it across the orders it actually covers.
# No counterpart in words, because the judge this demo hires is handed
# the attempted call and nothing else, and `Order.total` is in the
# store. That is this judge rather than judges in general: `Judge` is
# an interface, and one built over a store connection would settle
# this. What argues for leaving it an expression is that a query
# answers the comparison for nothing and answers it the same way
# twice.
# Stated in words below as `CreditWithinOrderTotalWithJudge`, and the
# twin is what the demo guards on. It is the rule that needs a judge
# able to read: `Order.total` is a number on record, so settling this
# in words means looking it up. What argues for leaving the arithmetic
# version here is that a query answers the comparison for nothing,
# answers it the same way twice, and -- once there is an evaluator --
# can answer it inside the transaction the write runs in, which is
# where two concurrent credits stop being able to pass separately and
# bust the total together.

- name: CreditUnderReviewThreshold
expression: amount <= 25
Expand All @@ -179,9 +189,21 @@ semantic_model:
# and guards are settled before the transaction opens. It wants to be
# checked inside the transaction, or declared in the schema.

# Rules stated in words, settled by a judge reading the attempted call.
# These are what `IssueCredit` guards on, and they carry all three
# consequences: escalate, warn and reject.
# Rules stated in words, settled by a judge reading the attempted call
# and, where the rule is about something on record, the record. These are
# what `IssueCredit` guards on, and they carry all three consequences:
# escalate, warn and reject.
- name: CreditWithinOrderTotalWithJudge
judgment: >-
The credit amount requested must not exceed the total of the order
it is applied to. The `order` argument of this call identifies that
order, and the order's total is on record rather than stated in the
arguments, so read it before answering. Read both as dollars.
on_violation: escalate
description: >-
A credit cannot exceed the total of the order it credits. Lower the
credit amount, or split it across the orders it actually covers.

- name: CreditUnderReviewThresholdWithJudge
judgment: >-
The credit amount requested must not exceed 25 dollars, which is the
Expand Down
Loading