@channela9/mogi 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/.github/workflows/publish.yml +28 -0
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/ai-interface.d.ts +96 -0
- package/ai-interface.ts +53 -0
- package/ai-models/ai-google.ts +214 -0
- package/mogi.d.ts +225 -0
- package/mogi.ts +447 -0
- package/package.json +38 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
name: Publish to npm
|
2
|
+
on:
|
3
|
+
release:
|
4
|
+
types: [published]
|
5
|
+
# Alternatively, trigger on a tag push:
|
6
|
+
# push:
|
7
|
+
# tags:
|
8
|
+
# - 'v*'
|
9
|
+
|
10
|
+
jobs:
|
11
|
+
publish:
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v4
|
15
|
+
|
16
|
+
- name: Setup Node.js
|
17
|
+
uses: actions/setup-node@v4
|
18
|
+
with:
|
19
|
+
node-version: 20
|
20
|
+
registry-url: https://registry.npmjs.org/
|
21
|
+
|
22
|
+
- name: Install Dependencies
|
23
|
+
run: npm ci
|
24
|
+
|
25
|
+
- name: Publish Package
|
26
|
+
run: npm publish
|
27
|
+
env:
|
28
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Shaun Colegado
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
# Mogi
|
2
|
+
|
3
|
+
Mogi is a simulation framework that allows you to create and run complex agent-based simulations using AI-driven nodes and processes. This README provides an overview of how to set up and use Mogi, including a detailed example of an amusement park simulation.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
To install Mogi, clone the repository and install the dependencies:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
git clone https://github.com/yourusername/mogi.git
|
11
|
+
cd mogi
|
12
|
+
npm install
|
13
|
+
```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
### Creating a Simulation
|
18
|
+
|
19
|
+
To create a simulation, you need to initialize the `MogiSimulation` class with a configuration object. You can then add agents and define processes and nodes that the agents will go through.
|
20
|
+
|
21
|
+
### Example: Amusement Park Simulation
|
22
|
+
|
23
|
+
Below is an example of how to set up an amusement park simulation where agents (riders) go through ticketing, ride, and exit nodes.
|
24
|
+
|
25
|
+
#### Step 1: Initialize AI Service
|
26
|
+
|
27
|
+
```typescript
|
28
|
+
import { GoogleAIService } from "./ai-interfaces/ai-google";
|
29
|
+
|
30
|
+
const AI = new GoogleAIService("YOUR_API_KEY", "gemini-2.0-flash-exp");
|
31
|
+
```
|
32
|
+
|
33
|
+
#### Step 2: Create Simulation Instance
|
34
|
+
|
35
|
+
```typescript
|
36
|
+
import { MogiSimulation } from "./mogi";
|
37
|
+
|
38
|
+
const simulation = new MogiSimulation({
|
39
|
+
description: "Amusement Park",
|
40
|
+
delayMs: 3000,
|
41
|
+
maxConcurrency: 2,
|
42
|
+
printLogs: true,
|
43
|
+
});
|
44
|
+
```
|
45
|
+
|
46
|
+
#### Step 3: Add Agents
|
47
|
+
|
48
|
+
```typescript
|
49
|
+
const rider1Id = simulation.addAgent({
|
50
|
+
attributes: { name: "Jake", money: 10, happiness: 5, height: 140, fatigue: 0 },
|
51
|
+
}, "Jake");
|
52
|
+
|
53
|
+
const rider2Id = simulation.addAgent({
|
54
|
+
attributes: { name: "John", money: 10, happiness: 5, height: 180, fatigue: 0 },
|
55
|
+
}, "John");
|
56
|
+
```
|
57
|
+
|
58
|
+
#### Step 4: Create Nodes
|
59
|
+
|
60
|
+
```typescript
|
61
|
+
import { createMogiNode } from "./mogi";
|
62
|
+
import { SchemaType } from "@google/generative-ai";
|
63
|
+
|
64
|
+
const ticketingNode = createMogiNode(
|
65
|
+
'ticketBox',
|
66
|
+
AI,
|
67
|
+
`
|
68
|
+
As ticketing staff:
|
69
|
+
- Verify rider height (150-200cm)
|
70
|
+
- Charge $2 if valid
|
71
|
+
- Update worker fatigue (+1)
|
72
|
+
- Update rider money and happiness
|
73
|
+
`,
|
74
|
+
{
|
75
|
+
ticket: {
|
76
|
+
type: SchemaType.STRING,
|
77
|
+
enum: ["standard", "premium"],
|
78
|
+
},
|
79
|
+
money: {
|
80
|
+
type: SchemaType.NUMBER,
|
81
|
+
}
|
82
|
+
},
|
83
|
+
{ useChainOfThought: true }
|
84
|
+
);
|
85
|
+
|
86
|
+
const rideNode = createMogiNode(
|
87
|
+
'rideOperator',
|
88
|
+
AI,
|
89
|
+
`
|
90
|
+
As ride operator:
|
91
|
+
- Simulate ride experience
|
92
|
+
- Increase rider happiness (1-5 points)
|
93
|
+
- Increase rider fatigue (1-3 points)
|
94
|
+
`,
|
95
|
+
{
|
96
|
+
happiness: { type: SchemaType.NUMBER },
|
97
|
+
fatigue: { type: SchemaType.NUMBER },
|
98
|
+
},
|
99
|
+
{ useChainOfThought: false }
|
100
|
+
);
|
101
|
+
|
102
|
+
const exitNode = createMogiNode(
|
103
|
+
'exitStaff',
|
104
|
+
AI,
|
105
|
+
`
|
106
|
+
As exit staff:
|
107
|
+
- Give farewell greeting
|
108
|
+
- Final happiness adjustment (±1)
|
109
|
+
`,
|
110
|
+
{
|
111
|
+
happiness: { type: SchemaType.NUMBER },
|
112
|
+
fatigue: { type: SchemaType.NUMBER },
|
113
|
+
},
|
114
|
+
{ useChainOfThought: false }
|
115
|
+
);
|
116
|
+
```
|
117
|
+
|
118
|
+
#### Step 5: Create Processes
|
119
|
+
|
120
|
+
```typescript
|
121
|
+
import { MogiProcess } from "./mogi";
|
122
|
+
|
123
|
+
const fullRideProcess = new MogiProcess("full-ride")
|
124
|
+
.addNode(ticketingNode)
|
125
|
+
.addNode(rideNode)
|
126
|
+
.addNode(exitNode);
|
127
|
+
|
128
|
+
const shortProcess = new MogiProcess("short-exit").addNode(exitNode);
|
129
|
+
```
|
130
|
+
|
131
|
+
#### Step 6: Create Conditional Process
|
132
|
+
|
133
|
+
```typescript
|
134
|
+
import { MogiConditionalProcess } from "./mogi";
|
135
|
+
|
136
|
+
const heightCondition = (min: number, max: number) => {
|
137
|
+
return (agent: MogiAgentState) => {
|
138
|
+
const height = agent.attributes.height;
|
139
|
+
if (height) return height >= min && height <= max;
|
140
|
+
return false;
|
141
|
+
};
|
142
|
+
};
|
143
|
+
|
144
|
+
const amusementParkFlow = new MogiConditionalProcess(
|
145
|
+
"height-check-flow",
|
146
|
+
heightCondition(150, 200),
|
147
|
+
fullRideProcess,
|
148
|
+
shortProcess
|
149
|
+
);
|
150
|
+
```
|
151
|
+
|
152
|
+
#### Step 7: Run Simulation
|
153
|
+
|
154
|
+
```typescript
|
155
|
+
simulation.runProcess(amusementParkFlow).then(() => {
|
156
|
+
console.log("Simulation completed\n");
|
157
|
+
|
158
|
+
// Helper to display agent history
|
159
|
+
const logHistory = (agentId: string) => {
|
160
|
+
const agent = simulation.getAgentState(agentId);
|
161
|
+
console.log(`\n${agent.attributes.name}'s journey:`);
|
162
|
+
agent.history.forEach((entry) => {
|
163
|
+
console.log(`[${entry.timestamp.toLocaleTimeString()}] ${JSON.stringify(entry.changes)}`);
|
164
|
+
});
|
165
|
+
console.log(agent.attributes);
|
166
|
+
};
|
167
|
+
|
168
|
+
logHistory(rider1Id);
|
169
|
+
logHistory(rider2Id);
|
170
|
+
|
171
|
+
const usage = simulation.getAIUsage();
|
172
|
+
console.log(`API Usage:
|
173
|
+
Calls: ${usage.totalCalls}
|
174
|
+
Input tokens: ${usage.totalInputTokens}
|
175
|
+
Output tokens: ${usage.totalOutputTokens}
|
176
|
+
Estimated cost: $${usage.estimatedCost.toFixed(4)}`);
|
177
|
+
});
|
178
|
+
```
|
179
|
+
|
180
|
+
## License
|
181
|
+
|
182
|
+
This project is licensed under the MIT License.
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import { Schema } from "@google/generative-ai";
|
2
|
+
|
3
|
+
export type AIPromptResponse = string | string[];
|
4
|
+
export type TokenUsage = { input: number; output: number };
|
5
|
+
|
6
|
+
export interface AIUsageStats {
|
7
|
+
totalCalls: number;
|
8
|
+
totalInputTokens: number;
|
9
|
+
totalOutputTokens: number;
|
10
|
+
estimatedCost: number;
|
11
|
+
}
|
12
|
+
|
13
|
+
export type AIInterfaceModelsDefinition = {
|
14
|
+
primary: string;
|
15
|
+
utility: string;
|
16
|
+
};
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Abstract class representing an AI service.
|
20
|
+
* All AI services should implement as singletons.
|
21
|
+
*/
|
22
|
+
export abstract class AIService {
|
23
|
+
protected abstract usageStats: AIUsageStats;
|
24
|
+
static instance: AIService;
|
25
|
+
/**
|
26
|
+
* Creates a schema from an object.
|
27
|
+
* @param object - The object to create a schema from.
|
28
|
+
* @returns The created schema.
|
29
|
+
*/
|
30
|
+
abstract createSchema(object: object): Schema;
|
31
|
+
/**
|
32
|
+
* Prompts the AI service with a system message, content, and instruction.
|
33
|
+
* @param system - The system message.
|
34
|
+
* @param content - The content to process.
|
35
|
+
* @param instruction - The instruction for the AI.
|
36
|
+
* @param schema - The schema for the response.
|
37
|
+
* @returns The AI prompt response.
|
38
|
+
*/
|
39
|
+
abstract prompt(
|
40
|
+
system: string,
|
41
|
+
content: string,
|
42
|
+
instruction: string,
|
43
|
+
schema?: Schema
|
44
|
+
): Promise<AIPromptResponse>;
|
45
|
+
/**
|
46
|
+
* Prompts the AI service with a system message, content, and instruction, and generates reasoning.
|
47
|
+
* @param system - The system message.
|
48
|
+
* @param content - The content to process.
|
49
|
+
* @param instruction - The instruction for the AI.
|
50
|
+
* @param schema - The schema for the response.
|
51
|
+
* @returns The AI prompt response with reasoning.
|
52
|
+
*/
|
53
|
+
abstract promptThinking(
|
54
|
+
system: string,
|
55
|
+
content: string,
|
56
|
+
instruction: string,
|
57
|
+
schema?: Schema
|
58
|
+
): Promise<AIPromptResponse>;
|
59
|
+
/**
|
60
|
+
* Sets the primary model for the AI service.
|
61
|
+
* @param model - The primary model.
|
62
|
+
*/
|
63
|
+
abstract setPrimaryModel(model: string): void;
|
64
|
+
/**
|
65
|
+
* Sets the utility model for the AI service.
|
66
|
+
* @param model - The utility model.
|
67
|
+
*/
|
68
|
+
abstract setUtilityModel(model: string): void;
|
69
|
+
/**
|
70
|
+
* Sets the generation configuration for the AI service.
|
71
|
+
* @param config - The generation configuration.
|
72
|
+
*/
|
73
|
+
abstract setGenerationConfig(config: object): void;
|
74
|
+
/**
|
75
|
+
* Calculates the cost of the AI service based on input and output tokens.
|
76
|
+
* @param inputTokens - The number of input tokens.
|
77
|
+
* @param outputTokens - The number of output tokens.
|
78
|
+
* @returns The calculated cost.
|
79
|
+
*/
|
80
|
+
abstract calculateCost(inputTokens: number, outputTokens: number): number;
|
81
|
+
/**
|
82
|
+
* Sets the pricing for the AI service.
|
83
|
+
* @param input - The cost per million input tokens.
|
84
|
+
* @param output - The cost per million output tokens.
|
85
|
+
*/
|
86
|
+
abstract setPricing(input: number, output: number): void;
|
87
|
+
/**
|
88
|
+
* Gets the usage statistics for the AI service.
|
89
|
+
* @returns The usage statistics.
|
90
|
+
*/
|
91
|
+
getUsageStats(): AIUsageStats;
|
92
|
+
/**
|
93
|
+
* Resets the usage statistics for the AI service.
|
94
|
+
*/
|
95
|
+
resetUsageStats(): void;
|
96
|
+
}
|
package/ai-interface.ts
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
// ai.ts - Universal AI Interface
|
2
|
+
import { SchemaType, Schema } from "@google/generative-ai";
|
3
|
+
|
4
|
+
export type AIPromptResponse = string | string[];
|
5
|
+
export type TokenUsage = { input: number; output: number };
|
6
|
+
|
7
|
+
export interface AIUsageStats {
|
8
|
+
totalCalls: number;
|
9
|
+
totalInputTokens: number;
|
10
|
+
totalOutputTokens: number;
|
11
|
+
estimatedCost: number;
|
12
|
+
}
|
13
|
+
|
14
|
+
export type AIInterfaceModelsDefinition = {
|
15
|
+
primary: string;
|
16
|
+
utility: string;
|
17
|
+
};
|
18
|
+
|
19
|
+
// all AI services should implement as singletons
|
20
|
+
export abstract class AIService {
|
21
|
+
protected abstract usageStats: AIUsageStats;
|
22
|
+
static instance: AIService;
|
23
|
+
abstract createSchema(object: object): Schema;
|
24
|
+
abstract prompt(
|
25
|
+
system: string,
|
26
|
+
content: string,
|
27
|
+
instruction: string,
|
28
|
+
schema?: Schema
|
29
|
+
): Promise<AIPromptResponse>;
|
30
|
+
abstract promptThinking(
|
31
|
+
system: string,
|
32
|
+
content: string,
|
33
|
+
instruction: string,
|
34
|
+
schema?: Schema
|
35
|
+
): Promise<AIPromptResponse>;
|
36
|
+
abstract setPrimaryModel(model: string): void;
|
37
|
+
abstract setUtilityModel(model: string): void;
|
38
|
+
abstract setGenerationConfig(config: object): void;
|
39
|
+
abstract calculateCost(inputTokens: number, outputTokens: number): number
|
40
|
+
abstract setPricing(input: number, output: number): void;
|
41
|
+
getUsageStats(): AIUsageStats {
|
42
|
+
return this.usageStats;
|
43
|
+
}
|
44
|
+
|
45
|
+
resetUsageStats(): void {
|
46
|
+
this.usageStats = {
|
47
|
+
totalCalls: 0,
|
48
|
+
totalInputTokens: 0,
|
49
|
+
totalOutputTokens: 0,
|
50
|
+
estimatedCost: 0,
|
51
|
+
};
|
52
|
+
}
|
53
|
+
}
|
@@ -0,0 +1,214 @@
|
|
1
|
+
// ai/gemini.ts
|
2
|
+
import {
|
3
|
+
GoogleGenerativeAI,
|
4
|
+
HarmCategory,
|
5
|
+
HarmBlockThreshold,
|
6
|
+
type GenerationConfig,
|
7
|
+
type SafetySetting,
|
8
|
+
type Schema,
|
9
|
+
SchemaType,
|
10
|
+
GenerateContentResponse,
|
11
|
+
} from "@google/generative-ai";
|
12
|
+
import { AIService, AIPromptResponse } from "../ai-interface";
|
13
|
+
|
14
|
+
const SAFETY_SETTINGS: SafetySetting[] = [
|
15
|
+
{
|
16
|
+
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
|
17
|
+
threshold: HarmBlockThreshold.BLOCK_NONE,
|
18
|
+
},
|
19
|
+
{
|
20
|
+
category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
|
21
|
+
threshold: HarmBlockThreshold.BLOCK_NONE,
|
22
|
+
},
|
23
|
+
{
|
24
|
+
category: HarmCategory.HARM_CATEGORY_HARASSMENT,
|
25
|
+
threshold: HarmBlockThreshold.BLOCK_NONE,
|
26
|
+
},
|
27
|
+
{
|
28
|
+
category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
|
29
|
+
threshold: HarmBlockThreshold.BLOCK_NONE,
|
30
|
+
},
|
31
|
+
];
|
32
|
+
|
33
|
+
const DEFAULT_CONFIG: GenerationConfig = {
|
34
|
+
temperature: 0.1,
|
35
|
+
topP: 1,
|
36
|
+
topK: 1,
|
37
|
+
frequencyPenalty: 0.5,
|
38
|
+
};
|
39
|
+
|
40
|
+
export class GoogleAIService extends AIService {
|
41
|
+
static instance: GoogleAIService;
|
42
|
+
|
43
|
+
protected usageStats = {
|
44
|
+
totalCalls: 0,
|
45
|
+
totalInputTokens: 0,
|
46
|
+
totalOutputTokens: 0,
|
47
|
+
estimatedCost: 0,
|
48
|
+
};
|
49
|
+
|
50
|
+
private genAI: GoogleGenerativeAI;
|
51
|
+
private models = {
|
52
|
+
primary: "gemini-1.5-flash",
|
53
|
+
utility: "gemini-1.5-flash",
|
54
|
+
};
|
55
|
+
private config: GenerationConfig = DEFAULT_CONFIG;
|
56
|
+
private modelPricing = {
|
57
|
+
inputCostPerMToken: 0.15,
|
58
|
+
outputCostPerMToken: 0.60, // pessimistic pricing calculation. highest pricing bracket for Gemini 1.5 Pro >128k token prompts
|
59
|
+
};
|
60
|
+
|
61
|
+
constructor(apiKey: string, model: string, subModel?: string) {
|
62
|
+
super();
|
63
|
+
|
64
|
+
if (GoogleAIService.instance) {
|
65
|
+
return GoogleAIService.instance;
|
66
|
+
}
|
67
|
+
|
68
|
+
this.genAI = new GoogleGenerativeAI(apiKey);
|
69
|
+
|
70
|
+
this.models.primary = model;
|
71
|
+
if (subModel) {
|
72
|
+
this.models.utility = subModel;
|
73
|
+
} else {
|
74
|
+
this.models.utility = model;
|
75
|
+
}
|
76
|
+
|
77
|
+
GoogleAIService.instance = this;
|
78
|
+
}
|
79
|
+
|
80
|
+
createSchema(obj: object): Schema {
|
81
|
+
const properties: Record<string, Schema> = {};
|
82
|
+
for (const [key, value] of Object.entries(obj)) {
|
83
|
+
properties[key] = this.inferSchema(value);
|
84
|
+
}
|
85
|
+
return { type: SchemaType.OBJECT, properties, nullable: false };
|
86
|
+
}
|
87
|
+
|
88
|
+
private inferSchema(value: unknown): Schema {
|
89
|
+
switch (typeof value) {
|
90
|
+
case "string":
|
91
|
+
return { type: SchemaType.STRING };
|
92
|
+
case "number":
|
93
|
+
return { type: SchemaType.NUMBER };
|
94
|
+
case "boolean":
|
95
|
+
return { type: SchemaType.BOOLEAN };
|
96
|
+
case "object":
|
97
|
+
if (Array.isArray(value)) {
|
98
|
+
return {
|
99
|
+
type: SchemaType.ARRAY,
|
100
|
+
items: value.length > 0 ? this.inferSchema(value[0]) : { type: SchemaType.STRING },
|
101
|
+
};
|
102
|
+
}
|
103
|
+
return value ? this.createSchema(value) : { type: SchemaType.OBJECT };
|
104
|
+
default:
|
105
|
+
return { type: SchemaType.STRING };
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
async prompt(
|
110
|
+
system: string,
|
111
|
+
content: string,
|
112
|
+
instruction: string,
|
113
|
+
schema?: Schema
|
114
|
+
): Promise<AIPromptResponse> {
|
115
|
+
const model = this.genAI.getGenerativeModel({
|
116
|
+
model: this.models.primary,
|
117
|
+
generationConfig: DEFAULT_CONFIG,
|
118
|
+
safetySettings: SAFETY_SETTINGS,
|
119
|
+
});
|
120
|
+
|
121
|
+
try {
|
122
|
+
const result = await model.generateContent({
|
123
|
+
contents: [
|
124
|
+
{ role: "model", parts: [{ text: system }] },
|
125
|
+
{ role: "user", parts: [{ text: content }] },
|
126
|
+
{ role: "user", parts: [{ text: instruction }] },
|
127
|
+
],
|
128
|
+
// systemInstruction: "Respond with valid JSON only",
|
129
|
+
generationConfig: {
|
130
|
+
responseMimeType: "application/json",
|
131
|
+
responseSchema: schema,
|
132
|
+
},
|
133
|
+
});
|
134
|
+
|
135
|
+
this.updateUsage(result.response);
|
136
|
+
|
137
|
+
return result.response.text();
|
138
|
+
} catch (error) {
|
139
|
+
console.error("Gemini prompt error:", error);
|
140
|
+
return JSON.stringify({ error: "AI processing failed" });
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
async promptThinking(
|
145
|
+
system: string,
|
146
|
+
content: string,
|
147
|
+
instruction: string,
|
148
|
+
schema?: Schema
|
149
|
+
): Promise<AIPromptResponse> {
|
150
|
+
const result = await this.prompt(system, content, instruction, schema);
|
151
|
+
const reasoning = await this.generateReasoning(system, `You added: ${result} to ${content}`);
|
152
|
+
|
153
|
+
console.log(result, reasoning);
|
154
|
+
return Array.isArray(result) ? [...result, reasoning] : [result, reasoning];
|
155
|
+
}
|
156
|
+
|
157
|
+
private async generateReasoning(system: string, content: string): Promise<string> {
|
158
|
+
const model = this.genAI.getGenerativeModel({
|
159
|
+
model: this.models.utility,
|
160
|
+
generationConfig: { ...DEFAULT_CONFIG, ...this.config, maxOutputTokens: 60 },
|
161
|
+
safetySettings: SAFETY_SETTINGS,
|
162
|
+
});
|
163
|
+
|
164
|
+
const result = await model.generateContent([
|
165
|
+
`ROLE: ${system}\n\n ${content}`,
|
166
|
+
"In natural language and in character, generate a very short statement that contextualizes the change made.",
|
167
|
+
]);
|
168
|
+
|
169
|
+
this.updateUsage(result.response);
|
170
|
+
|
171
|
+
return result.response.text();
|
172
|
+
}
|
173
|
+
|
174
|
+
setPrimaryModel(model: string): void {
|
175
|
+
this.models.primary = model;
|
176
|
+
}
|
177
|
+
setUtilityModel(model: string): void {
|
178
|
+
this.models.utility = model;
|
179
|
+
}
|
180
|
+
setGenerationConfig(config: object): void {
|
181
|
+
this.config = config;
|
182
|
+
}
|
183
|
+
|
184
|
+
// API Usage Tracking
|
185
|
+
setPricing(input: number, output: number): void {
|
186
|
+
this.modelPricing = {
|
187
|
+
inputCostPerMToken: input,
|
188
|
+
outputCostPerMToken: output,
|
189
|
+
};
|
190
|
+
}
|
191
|
+
|
192
|
+
private updateUsage(response: GenerateContentResponse): void {
|
193
|
+
const usage = response.usageMetadata || {
|
194
|
+
promptTokenCount: 0,
|
195
|
+
candidatesTokenCount: 0,
|
196
|
+
};
|
197
|
+
|
198
|
+
this.usageStats.totalCalls++;
|
199
|
+
this.usageStats.totalInputTokens += usage.promptTokenCount;
|
200
|
+
this.usageStats.totalOutputTokens += usage.candidatesTokenCount;
|
201
|
+
|
202
|
+
this.usageStats.estimatedCost += this.calculateCost(
|
203
|
+
usage.promptTokenCount,
|
204
|
+
usage.candidatesTokenCount
|
205
|
+
);
|
206
|
+
}
|
207
|
+
|
208
|
+
calculateCost(inputTokens: number, outputTokens: number): number {
|
209
|
+
return (
|
210
|
+
(inputTokens / 1_000_000) * this.modelPricing.inputCostPerMToken +
|
211
|
+
(outputTokens / 1_000_000) * this.modelPricing.outputCostPerMToken
|
212
|
+
);
|
213
|
+
}
|
214
|
+
}
|
package/mogi.d.ts
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
import { Schema } from "@google/generative-ai";
|
2
|
+
import { AIService, AIPromptResponse } from "./ai-interface";
|
3
|
+
import { AIUsageStats } from "./ai-interface";
|
4
|
+
|
5
|
+
declare type MogiAgentID = string;
|
6
|
+
declare type MogiProcessID = string;
|
7
|
+
declare type MogiNodeID = string;
|
8
|
+
|
9
|
+
export interface MogiAgentState {
|
10
|
+
id: MogiAgentID;
|
11
|
+
attributes: Record<string, any>;
|
12
|
+
history: MogiAgentHistory[];
|
13
|
+
}
|
14
|
+
|
15
|
+
export interface MogiAgentHistory {
|
16
|
+
timestamp: Date;
|
17
|
+
processId?: MogiProcessID;
|
18
|
+
nodeId?: MogiNodeID;
|
19
|
+
changes: Record<string, any>;
|
20
|
+
reasoning?: string;
|
21
|
+
}
|
22
|
+
|
23
|
+
export interface MogiSimulationConfig {
|
24
|
+
id: string;
|
25
|
+
description: string;
|
26
|
+
delayMs: number;
|
27
|
+
maxConcurrency: number;
|
28
|
+
printLogs: boolean;
|
29
|
+
}
|
30
|
+
|
31
|
+
export interface MogiSimulationState {
|
32
|
+
currentProcessId: MogiProcessID | null;
|
33
|
+
activeAgents: MogiAgentID[];
|
34
|
+
nodeIndex: number;
|
35
|
+
isComplete: boolean;
|
36
|
+
}
|
37
|
+
|
38
|
+
export interface MogiProcessConfig {
|
39
|
+
retries: number;
|
40
|
+
timeoutMs: number;
|
41
|
+
}
|
42
|
+
|
43
|
+
export interface MogiNodeConfig {
|
44
|
+
aiService: AIService;
|
45
|
+
instructions: string;
|
46
|
+
appendMessage?: string;
|
47
|
+
schema?: Schema;
|
48
|
+
useChainOfThought: boolean;
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Class representing the Mogi simulation.
|
53
|
+
*/
|
54
|
+
export class MogiSimulation {
|
55
|
+
constructor(config?: Partial<MogiSimulationConfig>);
|
56
|
+
/**
|
57
|
+
* Adds an agent to the simulation.
|
58
|
+
* @param initialState - The initial state of the agent.
|
59
|
+
* @returns The ID of the added agent.
|
60
|
+
*/
|
61
|
+
addAgent(initialState: Omit<MogiAgentState, "id" | "history">): MogiAgentID;
|
62
|
+
/**
|
63
|
+
* Gets the state of an agent by ID.
|
64
|
+
* @param id - The ID of the agent.
|
65
|
+
* @returns The state of the agent.
|
66
|
+
*/
|
67
|
+
getAgentState(id: MogiAgentID): MogiAgentState;
|
68
|
+
/**
|
69
|
+
* Gets the state of all agents in the simulation.
|
70
|
+
* @returns An array of agent states.
|
71
|
+
*/
|
72
|
+
getAllAgents(): MogiAgentState[];
|
73
|
+
/**
|
74
|
+
* Gets the current state of the simulation.
|
75
|
+
* @returns The state of the simulation.
|
76
|
+
*/
|
77
|
+
getSimulationState(): MogiSimulationState;
|
78
|
+
/**
|
79
|
+
* Initializes a process in the simulation.
|
80
|
+
* @param process - The process to initialize.
|
81
|
+
*/
|
82
|
+
initializeProcess(process: MogiProcess): Promise<void>;
|
83
|
+
/**
|
84
|
+
* Executes a single step of the simulation.
|
85
|
+
* @returns A promise that resolves to a boolean indicating if there are more steps to execute.
|
86
|
+
*/
|
87
|
+
executeStep(): Promise<boolean>;
|
88
|
+
/**
|
89
|
+
* Runs a process in the simulation.
|
90
|
+
* @param process - The process to run.
|
91
|
+
*/
|
92
|
+
runProcess(process: MogiProcess): Promise<void>;
|
93
|
+
/**
|
94
|
+
* Tracks an AI service for usage statistics.
|
95
|
+
* @param service - The AI service to track.
|
96
|
+
*/
|
97
|
+
private trackAIService(service: AIService): void;
|
98
|
+
/**
|
99
|
+
* Gets the usage statistics for all tracked AI services.
|
100
|
+
* @returns The usage statistics.
|
101
|
+
*/
|
102
|
+
getAIUsage(): AIUsageStats;
|
103
|
+
/**
|
104
|
+
* Resets the usage statistics for all tracked AI services.
|
105
|
+
*/
|
106
|
+
resetAIUsage(): void;
|
107
|
+
}
|
108
|
+
|
109
|
+
/**
|
110
|
+
* Class representing a process in the Mogi simulation.
|
111
|
+
*/
|
112
|
+
export class MogiProcess {
|
113
|
+
constructor(id: MogiProcessID, config?: MogiProcessConfig);
|
114
|
+
/**
|
115
|
+
* Adds a node to the process.
|
116
|
+
* @param node - The node to add.
|
117
|
+
* @returns The process instance.
|
118
|
+
*/
|
119
|
+
addNode(node: MogiNode): this;
|
120
|
+
/**
|
121
|
+
* Executes the process for a set of agents.
|
122
|
+
* @param agents - The agents to process.
|
123
|
+
* @param simConfig - The simulation configuration.
|
124
|
+
*/
|
125
|
+
execute(agents: MogiAgentState[], simConfig: MogiSimulationConfig): Promise<void>;
|
126
|
+
/**
|
127
|
+
* Resets the execution state of the process.
|
128
|
+
*/
|
129
|
+
resetExecution(): void;
|
130
|
+
/**
|
131
|
+
* Executes a single step for an agent in the process.
|
132
|
+
* @param agent - The agent to process.
|
133
|
+
* @param simConfig - The simulation configuration.
|
134
|
+
* @returns A promise that resolves to a boolean indicating if there are more steps to execute.
|
135
|
+
*/
|
136
|
+
executeAgentStep(agent: MogiAgentState, simConfig: MogiSimulationConfig): Promise<boolean>;
|
137
|
+
/**
|
138
|
+
* Gets the AI services used by the process.
|
139
|
+
* @returns An array of AI services.
|
140
|
+
*/
|
141
|
+
getAIServices(): AIService[];
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Class representing a node in the Mogi simulation.
|
146
|
+
*/
|
147
|
+
export class MogiNode {
|
148
|
+
constructor(
|
149
|
+
id: MogiNodeID,
|
150
|
+
config: MogiNodeConfig
|
151
|
+
);
|
152
|
+
/**
|
153
|
+
* Executes the node for an agent.
|
154
|
+
* @param agent - The agent to process.
|
155
|
+
* @param delayMs - The delay in milliseconds before processing.
|
156
|
+
*/
|
157
|
+
execute(agent: MogiAgentState, delayMs: number): Promise<void>;
|
158
|
+
/**
|
159
|
+
* Gets the AI service used by the node.
|
160
|
+
* @returns The AI service.
|
161
|
+
*/
|
162
|
+
getAIService(): AIService;
|
163
|
+
}
|
164
|
+
|
165
|
+
/**
|
166
|
+
* Creates a Mogi node with AI-driven instructions.
|
167
|
+
* @param ai - The AI service to use.
|
168
|
+
* @param instructions - The instructions for the node.
|
169
|
+
* @param schema - The schema for the node.
|
170
|
+
* @returns The created Mogi node.
|
171
|
+
*/
|
172
|
+
export function createMogiNode(
|
173
|
+
ai: AIService,
|
174
|
+
instructions: string,
|
175
|
+
schema?: Schema
|
176
|
+
): MogiNode;
|
177
|
+
|
178
|
+
/**
|
179
|
+
* Utility function to encapsulate schemas.
|
180
|
+
* @param schemas - The schemas to encapsulate.
|
181
|
+
* @returns The encapsulated schema.
|
182
|
+
*/
|
183
|
+
export function encapsulateSchema(schemas: { [k: string]: Schema }): Schema;
|
184
|
+
|
185
|
+
/**
|
186
|
+
* Class representing a conditional process in the Mogi simulation.
|
187
|
+
*/
|
188
|
+
export class MogiConditionalProcess extends MogiProcess {
|
189
|
+
constructor(
|
190
|
+
id: string,
|
191
|
+
condition: (agent: MogiAgentState) => boolean,
|
192
|
+
trueBranch: MogiProcess,
|
193
|
+
falseBranch: MogiProcess,
|
194
|
+
nodes?: MogiNode[],
|
195
|
+
config?: MogiProcessConfig
|
196
|
+
);
|
197
|
+
/**
|
198
|
+
* Executes a conditional step for a set of agents.
|
199
|
+
* @param agents - The agents to process.
|
200
|
+
* @param simConfig - The simulation configuration.
|
201
|
+
* @returns A promise that resolves to a boolean indicating if there are more steps to execute.
|
202
|
+
*/
|
203
|
+
executeConditionalStep(agents: MogiAgentState[], simConfig: MogiSimulationConfig): Promise<boolean>;
|
204
|
+
/**
|
205
|
+
* Resets the execution state of the conditional process.
|
206
|
+
*/
|
207
|
+
resetExecution(): void;
|
208
|
+
/**
|
209
|
+
* Updates the branches of the conditional process.
|
210
|
+
* @param trueBranch - The new true branch process.
|
211
|
+
* @param falseBranch - The new false branch process.
|
212
|
+
*/
|
213
|
+
updateBranches(trueBranch?: MogiProcess, falseBranch?: MogiProcess): void;
|
214
|
+
/**
|
215
|
+
* Gets the active branch for an agent.
|
216
|
+
* @param agentId - The ID of the agent.
|
217
|
+
* @returns The active branch process.
|
218
|
+
*/
|
219
|
+
getActiveBranch(agentId: MogiAgentID): MogiProcess | null;
|
220
|
+
/**
|
221
|
+
* Gets the AI services used by the conditional process.
|
222
|
+
* @returns An array of AI services.
|
223
|
+
*/
|
224
|
+
getAIServices(): AIService[];
|
225
|
+
}
|
package/mogi.ts
ADDED
@@ -0,0 +1,447 @@
|
|
1
|
+
// mogi.ts
|
2
|
+
import { AIService, AIUsageStats, type AIPromptResponse } from "./ai-interface";
|
3
|
+
import { Schema, SchemaType } from "@google/generative-ai";
|
4
|
+
|
5
|
+
type MogiAgentID = string;
|
6
|
+
type MogiProcessID = string;
|
7
|
+
type MogiNodeID = string;
|
8
|
+
|
9
|
+
export interface MogiAgentState {
|
10
|
+
id: MogiAgentID;
|
11
|
+
attributes: Record<string, any>;
|
12
|
+
history: MogiAgentHistory[];
|
13
|
+
}
|
14
|
+
|
15
|
+
export interface MogiAgentHistory {
|
16
|
+
timestamp: Date;
|
17
|
+
processId?: MogiProcessID;
|
18
|
+
nodeId?: MogiNodeID;
|
19
|
+
changes: Record<string, any>;
|
20
|
+
reasoning?: string;
|
21
|
+
}
|
22
|
+
|
23
|
+
export interface MogiSimulationConfig {
|
24
|
+
id: string;
|
25
|
+
description: string;
|
26
|
+
delayMs: number;
|
27
|
+
maxConcurrency: number;
|
28
|
+
printLogs: boolean;
|
29
|
+
}
|
30
|
+
|
31
|
+
export interface MogiSimulationState {
|
32
|
+
currentProcessId: MogiProcessID | null;
|
33
|
+
activeAgents: MogiAgentID[];
|
34
|
+
nodeIndex: number;
|
35
|
+
isComplete: boolean;
|
36
|
+
}
|
37
|
+
|
38
|
+
export interface MogiProcessConfig {
|
39
|
+
retries: number;
|
40
|
+
timeoutMs: number;
|
41
|
+
}
|
42
|
+
|
43
|
+
export interface MogiNodeConfig {
|
44
|
+
aiService: AIService;
|
45
|
+
instructions: string;
|
46
|
+
appendMessage?: string;
|
47
|
+
schema?: Schema;
|
48
|
+
useChainOfThought: boolean;
|
49
|
+
}
|
50
|
+
|
51
|
+
export class MogiSimulation {
|
52
|
+
private aiServices: Set<AIService> = new Set();
|
53
|
+
private globalUsage: AIUsageStats = {
|
54
|
+
totalCalls: 0,
|
55
|
+
totalInputTokens: 0,
|
56
|
+
totalOutputTokens: 0,
|
57
|
+
estimatedCost: 0,
|
58
|
+
};
|
59
|
+
|
60
|
+
private executionQueue: Array<{
|
61
|
+
processId: MogiProcessID;
|
62
|
+
agentId: MogiAgentID;
|
63
|
+
nodeIndex: number;
|
64
|
+
}> = [];
|
65
|
+
private currentState: MogiSimulationState = {
|
66
|
+
currentProcessId: null,
|
67
|
+
activeAgents: [],
|
68
|
+
nodeIndex: 0,
|
69
|
+
isComplete: true,
|
70
|
+
};
|
71
|
+
|
72
|
+
private agents: Map<MogiAgentID, MogiAgentState> = new Map();
|
73
|
+
private processes: MogiProcess[] = [];
|
74
|
+
private config: MogiSimulationConfig;
|
75
|
+
|
76
|
+
constructor(config: Partial<MogiSimulationConfig> = {}) {
|
77
|
+
this.config = {
|
78
|
+
id: crypto.randomUUID(),
|
79
|
+
description: "Unnamed Simulation",
|
80
|
+
delayMs: 1500,
|
81
|
+
maxConcurrency: 5,
|
82
|
+
printLogs: false,
|
83
|
+
...config,
|
84
|
+
};
|
85
|
+
}
|
86
|
+
|
87
|
+
addAgent(
|
88
|
+
initialState: Omit<MogiAgentState, "id" | "history">,
|
89
|
+
id: string = crypto.randomUUID()
|
90
|
+
): MogiAgentID {
|
91
|
+
this.agents.set(id, {
|
92
|
+
id,
|
93
|
+
attributes: initialState.attributes,
|
94
|
+
history: [],
|
95
|
+
});
|
96
|
+
return id;
|
97
|
+
}
|
98
|
+
|
99
|
+
public getAgentState(id: MogiAgentID): MogiAgentState {
|
100
|
+
const agent = this.agents.get(id);
|
101
|
+
if (!agent) {
|
102
|
+
throw new Error(`Agent ${id} not found in simulation`);
|
103
|
+
}
|
104
|
+
return agent;
|
105
|
+
}
|
106
|
+
|
107
|
+
public getAllAgents(): MogiAgentState[] {
|
108
|
+
return Array.from(this.agents.values());
|
109
|
+
}
|
110
|
+
|
111
|
+
public getSimulationState(): MogiSimulationState {
|
112
|
+
return this.currentState;
|
113
|
+
}
|
114
|
+
|
115
|
+
public async initializeProcess(process: MogiProcess): Promise<void> {
|
116
|
+
this.currentState = {
|
117
|
+
currentProcessId: process.id,
|
118
|
+
activeAgents: Array.from(this.agents.keys()),
|
119
|
+
nodeIndex: 0,
|
120
|
+
isComplete: false,
|
121
|
+
};
|
122
|
+
this.executionQueue = [];
|
123
|
+
|
124
|
+
// Initialize AI Services
|
125
|
+
process.getAIServices().forEach((service) => this.trackAIService(service));
|
126
|
+
|
127
|
+
// Add Process
|
128
|
+
this.processes.push(process);
|
129
|
+
|
130
|
+
// Initialize execution queue
|
131
|
+
for (const agentId of this.currentState.activeAgents) {
|
132
|
+
this.executionQueue.push({
|
133
|
+
processId: process.id,
|
134
|
+
agentId,
|
135
|
+
nodeIndex: 0,
|
136
|
+
});
|
137
|
+
}
|
138
|
+
|
139
|
+
if (this.config.printLogs) {
|
140
|
+
console.log(
|
141
|
+
`Initialized process ${process.id} with agents: ${this.currentState.activeAgents.join(
|
142
|
+
", "
|
143
|
+
)}`
|
144
|
+
);
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
public async executeStep(): Promise<boolean> {
|
149
|
+
if (this.currentState.isComplete) return false;
|
150
|
+
|
151
|
+
const currentProcess = this.processes.find((p) => p.id === this.currentState.currentProcessId);
|
152
|
+
if (!currentProcess) {
|
153
|
+
this.currentState.isComplete = true;
|
154
|
+
return false;
|
155
|
+
}
|
156
|
+
|
157
|
+
// Handle conditional processes differently
|
158
|
+
if (currentProcess instanceof MogiConditionalProcess) {
|
159
|
+
return currentProcess.executeConditionalStep(
|
160
|
+
this.currentState.activeAgents.map((id) => this.getAgentState(id)),
|
161
|
+
this.config
|
162
|
+
);
|
163
|
+
}
|
164
|
+
|
165
|
+
// Original linear execution logic
|
166
|
+
const currentNode = currentProcess.nodes[this.currentState.nodeIndex];
|
167
|
+
const agents = this.currentState.activeAgents.map((id) => this.getAgentState(id));
|
168
|
+
|
169
|
+
await Promise.all(agents.map((agent) => currentNode.execute(agent, this.config.delayMs)));
|
170
|
+
|
171
|
+
this.currentState.nodeIndex++;
|
172
|
+
if (this.currentState.nodeIndex >= currentProcess.nodes.length) {
|
173
|
+
this.currentState.isComplete = true;
|
174
|
+
}
|
175
|
+
|
176
|
+
return !this.currentState.isComplete;
|
177
|
+
}
|
178
|
+
|
179
|
+
public async runProcess(process: MogiProcess): Promise<void> {
|
180
|
+
if (this.config.printLogs) console.log(`Process ${process.id} started.`);
|
181
|
+
await this.initializeProcess(process);
|
182
|
+
while (await this.executeStep()) {
|
183
|
+
if (this.config.printLogs)
|
184
|
+
console.log(`Process ${process.id}: Step ${this.currentState.nodeIndex}`);
|
185
|
+
await new Promise((resolve) => setTimeout(resolve, this.config.delayMs));
|
186
|
+
}
|
187
|
+
|
188
|
+
if (this.config.printLogs) console.log(`Process ${process.id} completed.`);
|
189
|
+
}
|
190
|
+
|
191
|
+
private trackAIService(service: AIService): void {
|
192
|
+
if (!this.aiServices.has(service)) {
|
193
|
+
this.aiServices.add(service);
|
194
|
+
}
|
195
|
+
}
|
196
|
+
|
197
|
+
public getAIUsage(): AIUsageStats {
|
198
|
+
return Array.from(this.aiServices).reduce(
|
199
|
+
(acc, service) => {
|
200
|
+
const stats = service.getUsageStats();
|
201
|
+
acc.totalCalls += stats.totalCalls;
|
202
|
+
acc.totalInputTokens += stats.totalInputTokens;
|
203
|
+
acc.totalOutputTokens += stats.totalOutputTokens;
|
204
|
+
acc.estimatedCost += stats.estimatedCost;
|
205
|
+
return acc;
|
206
|
+
},
|
207
|
+
{ ...this.globalUsage }
|
208
|
+
);
|
209
|
+
}
|
210
|
+
|
211
|
+
public resetAIUsage(): void {
|
212
|
+
this.aiServices.forEach((service) => service.resetUsageStats());
|
213
|
+
this.globalUsage = {
|
214
|
+
totalCalls: 0,
|
215
|
+
totalInputTokens: 0,
|
216
|
+
totalOutputTokens: 0,
|
217
|
+
estimatedCost: 0,
|
218
|
+
};
|
219
|
+
}
|
220
|
+
}
|
221
|
+
|
222
|
+
export class MogiProcess {
|
223
|
+
public nodes: MogiNode[] = [];
|
224
|
+
public currentNodeIndex = 0;
|
225
|
+
|
226
|
+
constructor(
|
227
|
+
public readonly id: MogiProcessID,
|
228
|
+
public config: MogiProcessConfig = { retries: 3, timeoutMs: 10000 }
|
229
|
+
) {}
|
230
|
+
|
231
|
+
addNode(node: MogiNode): this {
|
232
|
+
this.nodes.push(node);
|
233
|
+
return this;
|
234
|
+
}
|
235
|
+
|
236
|
+
async execute(agents: MogiAgentState[], simConfig: MogiSimulationConfig): Promise<void> {
|
237
|
+
for (const agent of agents) {
|
238
|
+
await this.runAgentThroughNodes(agent, simConfig);
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
private async runAgentThroughNodes(
|
243
|
+
agent: MogiAgentState,
|
244
|
+
simConfig: MogiSimulationConfig
|
245
|
+
): Promise<void> {
|
246
|
+
for (const node of this.nodes) {
|
247
|
+
await node.execute(agent, simConfig.delayMs);
|
248
|
+
await new Promise((resolve) => setTimeout(resolve, simConfig.delayMs));
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
public resetExecution(): void {
|
253
|
+
this.currentNodeIndex = 0;
|
254
|
+
}
|
255
|
+
|
256
|
+
public async executeAgentStep(
|
257
|
+
agent: MogiAgentState,
|
258
|
+
simConfig: MogiSimulationConfig
|
259
|
+
): Promise<boolean> {
|
260
|
+
if (this.currentNodeIndex < this.nodes.length) {
|
261
|
+
await this.nodes[this.currentNodeIndex].execute(agent, simConfig.delayMs);
|
262
|
+
this.currentNodeIndex++;
|
263
|
+
return true;
|
264
|
+
}
|
265
|
+
return false;
|
266
|
+
}
|
267
|
+
|
268
|
+
public getAIServices(): AIService[] {
|
269
|
+
return this.nodes.map((n) => n.getAIService());
|
270
|
+
}
|
271
|
+
}
|
272
|
+
|
273
|
+
export class MogiNode {
|
274
|
+
constructor(public readonly id: MogiNodeID, private config: MogiNodeConfig) {}
|
275
|
+
|
276
|
+
async execute(agent: MogiAgentState, delayMs: number): Promise<void> {
|
277
|
+
try {
|
278
|
+
const changes = await this.handler(agent);
|
279
|
+
this.applyChanges(agent, changes);
|
280
|
+
} catch (error) {
|
281
|
+
console.error(`MogiNode ${this.id} failed:`, error);
|
282
|
+
}
|
283
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
284
|
+
}
|
285
|
+
|
286
|
+
private applyChanges(agent: MogiAgentState, changes: Record<string, any>): void {
|
287
|
+
agent.attributes = { ...agent.attributes, ...changes };
|
288
|
+
agent.history.push({
|
289
|
+
timestamp: new Date(),
|
290
|
+
nodeId: this.id,
|
291
|
+
changes,
|
292
|
+
reasoning: changes._reasoning,
|
293
|
+
});
|
294
|
+
}
|
295
|
+
|
296
|
+
private async handler(agent: MogiAgentState) {
|
297
|
+
const response = this.config.useChainOfThought
|
298
|
+
? await this.config.aiService.promptThinking(
|
299
|
+
this.config.instructions,
|
300
|
+
JSON.stringify(agent.attributes),
|
301
|
+
this.config.appendMessage ??
|
302
|
+
"Process the JSON object and make changes based on your instructions.",
|
303
|
+
this.config.schema ?? this.config.aiService.createSchema(agent.attributes)
|
304
|
+
)
|
305
|
+
: await this.config.aiService.prompt(
|
306
|
+
this.config.instructions,
|
307
|
+
JSON.stringify(agent.attributes),
|
308
|
+
this.config.appendMessage ??
|
309
|
+
"Process the JSON object and make changes based on your instructions.",
|
310
|
+
this.config.schema ?? this.config.aiService.createSchema(agent.attributes)
|
311
|
+
);
|
312
|
+
return parseAIReponse(response);
|
313
|
+
}
|
314
|
+
|
315
|
+
public getAIService(): AIService {
|
316
|
+
return this.config.aiService;
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
// Utility functions
|
321
|
+
export function encapsulateSchema(schemas: { [k: string]: Schema }): Schema {
|
322
|
+
return {
|
323
|
+
type: SchemaType.OBJECT,
|
324
|
+
properties: schemas,
|
325
|
+
};
|
326
|
+
}
|
327
|
+
export function createMogiNode(
|
328
|
+
id: string = crypto.randomUUID(),
|
329
|
+
ai: AIService,
|
330
|
+
instructions: string,
|
331
|
+
schemas?: { [k: string]: Schema },
|
332
|
+
config?: Omit<MogiNodeConfig, "schema" | "aiService" | "instructions">
|
333
|
+
): MogiNode {
|
334
|
+
const newNode = new MogiNode(crypto.randomUUID(), {
|
335
|
+
aiService: ai,
|
336
|
+
instructions,
|
337
|
+
useChainOfThought: false,
|
338
|
+
appendMessage: config?.appendMessage,
|
339
|
+
schema: schemas ? encapsulateSchema(schemas) : undefined,
|
340
|
+
...config,
|
341
|
+
});
|
342
|
+
|
343
|
+
return newNode;
|
344
|
+
}
|
345
|
+
|
346
|
+
function parseAIReponse(response: AIPromptResponse): Record<string, any> {
|
347
|
+
try {
|
348
|
+
const [jsonStr, reasoning] = Array.isArray(response) ? response : [response];
|
349
|
+
const result = JSON.parse(jsonStr);
|
350
|
+
return { ...result, _reasoning: reasoning };
|
351
|
+
} catch (error) {
|
352
|
+
console.error("Failed to parse AI response:", error);
|
353
|
+
return { _error: "Invalid AI response", _raw: response };
|
354
|
+
}
|
355
|
+
}
|
356
|
+
|
357
|
+
export class MogiConditionalProcess extends MogiProcess {
|
358
|
+
private condition: (agent: MogiAgentState) => boolean;
|
359
|
+
private trueBranch: MogiProcess;
|
360
|
+
private falseBranch: MogiProcess;
|
361
|
+
private branchStates = new Map<
|
362
|
+
MogiAgentID,
|
363
|
+
{
|
364
|
+
branch: MogiProcess;
|
365
|
+
completed: boolean;
|
366
|
+
}
|
367
|
+
>();
|
368
|
+
|
369
|
+
constructor(
|
370
|
+
id: string,
|
371
|
+
condition: (agent: MogiAgentState) => boolean,
|
372
|
+
trueBranch: MogiProcess,
|
373
|
+
falseBranch: MogiProcess,
|
374
|
+
nodes: MogiNode[] = [],
|
375
|
+
config?: MogiProcessConfig
|
376
|
+
) {
|
377
|
+
super(id, config);
|
378
|
+
this.condition = condition;
|
379
|
+
this.trueBranch = trueBranch;
|
380
|
+
this.falseBranch = falseBranch;
|
381
|
+
nodes.forEach((n) => this.addNode(n));
|
382
|
+
}
|
383
|
+
|
384
|
+
public async executeConditionalStep(
|
385
|
+
agents: MogiAgentState[],
|
386
|
+
simConfig: MogiSimulationConfig
|
387
|
+
): Promise<boolean> {
|
388
|
+
let hasMoreSteps = false;
|
389
|
+
|
390
|
+
for (const agent of agents) {
|
391
|
+
if (this.currentNodeIndex < this.nodes.length) {
|
392
|
+
await this.nodes[this.currentNodeIndex].execute(agent, simConfig.delayMs);
|
393
|
+
await new Promise((resolve) => setTimeout(resolve, simConfig.delayMs));
|
394
|
+
hasMoreSteps = true;
|
395
|
+
continue;
|
396
|
+
}
|
397
|
+
|
398
|
+
// Initialize branch state if not set
|
399
|
+
if (!this.branchStates.has(agent.id)) {
|
400
|
+
const branch = this.condition(agent) ? this.trueBranch : this.falseBranch;
|
401
|
+
branch.resetExecution();
|
402
|
+
this.branchStates.set(agent.id, { branch, completed: false });
|
403
|
+
}
|
404
|
+
|
405
|
+
const branchState = this.branchStates.get(agent.id)!;
|
406
|
+
if (!branchState.completed) {
|
407
|
+
const branchHasSteps = await branchState.branch.executeAgentStep(agent, simConfig);
|
408
|
+
if (branchHasSteps) hasMoreSteps = true;
|
409
|
+
branchState.completed = !branchHasSteps;
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
// Advance main process index if still in base nodes
|
414
|
+
if (this.currentNodeIndex < this.nodes.length) {
|
415
|
+
this.currentNodeIndex++;
|
416
|
+
return true;
|
417
|
+
}
|
418
|
+
|
419
|
+
return hasMoreSteps;
|
420
|
+
}
|
421
|
+
|
422
|
+
public resetExecution(): void {
|
423
|
+
super.resetExecution();
|
424
|
+
this.branchStates.clear();
|
425
|
+
this.trueBranch.resetExecution();
|
426
|
+
this.falseBranch.resetExecution();
|
427
|
+
}
|
428
|
+
|
429
|
+
public updateBranches(trueBranch?: MogiProcess, falseBranch?: MogiProcess): void {
|
430
|
+
if (trueBranch) this.trueBranch = trueBranch;
|
431
|
+
if (falseBranch) this.falseBranch = falseBranch;
|
432
|
+
}
|
433
|
+
|
434
|
+
public getActiveBranch(agentId: MogiAgentID): MogiProcess | null {
|
435
|
+
return this.branchStates.get(agentId)?.branch || null;
|
436
|
+
}
|
437
|
+
|
438
|
+
public getAIServices(): AIService[] {
|
439
|
+
return [...this.nodes, this.trueBranch, this.falseBranch].flatMap((n) => {
|
440
|
+
if (n instanceof MogiProcess) {
|
441
|
+
return n.getAIServices();
|
442
|
+
} else {
|
443
|
+
return n.getAIService();
|
444
|
+
}
|
445
|
+
});
|
446
|
+
}
|
447
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
{
|
2
|
+
"name": "@channela9/mogi",
|
3
|
+
"version": "0.1.2",
|
4
|
+
"description": "A simulation framework that leverages LLMs to simulate various processes and interactions between agents.",
|
5
|
+
"main": "mogi.ts",
|
6
|
+
"dependencies": {
|
7
|
+
"@google/generative-ai": "^0.21.0"
|
8
|
+
},
|
9
|
+
"devDependencies": {
|
10
|
+
"ts-node": "^10.0.0",
|
11
|
+
"typescript": "^4.0.0"
|
12
|
+
},
|
13
|
+
"keywords": [
|
14
|
+
"simulation",
|
15
|
+
"AI",
|
16
|
+
"LLM",
|
17
|
+
"agents",
|
18
|
+
"process",
|
19
|
+
"interaction",
|
20
|
+
"framework",
|
21
|
+
"nodejs",
|
22
|
+
"typescript"
|
23
|
+
],
|
24
|
+
"scripts": {
|
25
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
26
|
+
},
|
27
|
+
"repository": {
|
28
|
+
"type": "git",
|
29
|
+
"url": "git+https://github.com/channelA9/mogi.git"
|
30
|
+
},
|
31
|
+
"author": "",
|
32
|
+
"license": "MIT",
|
33
|
+
"types": "./mogi.d.ts",
|
34
|
+
"bugs": {
|
35
|
+
"url": "https://github.com/channelA9/mogi/issues"
|
36
|
+
},
|
37
|
+
"homepage": "https://github.com/channelA9/mogi#readme"
|
38
|
+
}
|