@cogitator-ai/self-modifying 17.0.8 → 17.0.11
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/README.md +42 -1
- package/dist/architecture-evolution/capability-analyzer.d.ts +1 -1
- package/dist/architecture-evolution/capability-analyzer.d.ts.map +1 -1
- package/dist/architecture-evolution/capability-analyzer.js +4 -3
- package/dist/architecture-evolution/capability-analyzer.js.map +1 -1
- package/dist/architecture-evolution/evolution-strategy.d.ts.map +1 -1
- package/dist/architecture-evolution/evolution-strategy.js +3 -2
- package/dist/architecture-evolution/evolution-strategy.js.map +1 -1
- package/dist/architecture-evolution/parameter-optimizer.d.ts +1 -0
- package/dist/architecture-evolution/parameter-optimizer.d.ts.map +1 -1
- package/dist/architecture-evolution/parameter-optimizer.js +7 -4
- package/dist/architecture-evolution/parameter-optimizer.js.map +1 -1
- package/dist/architecture-evolution/prompts.d.ts.map +1 -1
- package/dist/architecture-evolution/prompts.js +7 -6
- package/dist/architecture-evolution/prompts.js.map +1 -1
- package/dist/constraints/index.d.ts +1 -1
- package/dist/constraints/index.d.ts.map +1 -1
- package/dist/constraints/index.js +1 -1
- package/dist/constraints/index.js.map +1 -1
- package/dist/constraints/modification-validator.d.ts.map +1 -1
- package/dist/constraints/modification-validator.js +12 -21
- package/dist/constraints/modification-validator.js.map +1 -1
- package/dist/constraints/safety-constraints.d.ts +2 -1
- package/dist/constraints/safety-constraints.d.ts.map +1 -1
- package/dist/constraints/safety-constraints.js +11 -1
- package/dist/constraints/safety-constraints.js.map +1 -1
- package/dist/events/event-emitter.d.ts.map +1 -1
- package/dist/events/event-emitter.js +8 -1
- package/dist/events/event-emitter.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/meta-reasoning/meta-reasoner.d.ts.map +1 -1
- package/dist/meta-reasoning/meta-reasoner.js +6 -0
- package/dist/meta-reasoning/meta-reasoner.js.map +1 -1
- package/dist/meta-reasoning/prompts.d.ts.map +1 -1
- package/dist/meta-reasoning/prompts.js +37 -3
- package/dist/meta-reasoning/prompts.js.map +1 -1
- package/dist/self-modifying-agent.d.ts +2 -1
- package/dist/self-modifying-agent.d.ts.map +1 -1
- package/dist/self-modifying-agent.js +122 -13
- package/dist/self-modifying-agent.js.map +1 -1
- package/dist/tool-generation/generated-tool-store.d.ts.map +1 -1
- package/dist/tool-generation/generated-tool-store.js +2 -4
- package/dist/tool-generation/generated-tool-store.js.map +1 -1
- package/dist/tool-generation/prompts.d.ts +1 -1
- package/dist/tool-generation/prompts.d.ts.map +1 -1
- package/dist/tool-generation/prompts.js +26 -21
- package/dist/tool-generation/prompts.js.map +1 -1
- package/dist/tool-generation/tool-generator.d.ts +1 -1
- package/dist/tool-generation/tool-generator.d.ts.map +1 -1
- package/dist/tool-generation/tool-generator.js +12 -13
- package/dist/tool-generation/tool-generator.js.map +1 -1
- package/dist/tool-generation/tool-sandbox.d.ts.map +1 -1
- package/dist/tool-generation/tool-sandbox.js +6 -2
- package/dist/tool-generation/tool-sandbox.js.map +1 -1
- package/dist/tool-generation/tool-validator.d.ts.map +1 -1
- package/dist/tool-generation/tool-validator.js +0 -3
- package/dist/tool-generation/tool-validator.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +34 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/llm-helper.d.ts +1 -0
- package/dist/utils/llm-helper.d.ts.map +1 -1
- package/dist/utils/llm-helper.js +1 -1
- package/dist/utils/llm-helper.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/events.test.ts +112 -0
- package/src/__tests__/self-modifying-agent.test.ts +197 -0
- package/src/__tests__/utils.test.ts +100 -0
- package/src/architecture-evolution/capability-analyzer.ts +5 -3
- package/src/architecture-evolution/evolution-strategy.ts +3 -2
- package/src/architecture-evolution/parameter-optimizer.ts +8 -7
- package/src/architecture-evolution/prompts.ts +7 -6
- package/src/constraints/index.ts +1 -0
- package/src/constraints/modification-validator.ts +13 -22
- package/src/constraints/safety-constraints.ts +16 -1
- package/src/events/event-emitter.ts +9 -1
- package/src/index.ts +3 -0
- package/src/meta-reasoning/meta-reasoner.ts +9 -0
- package/src/meta-reasoning/prompts.ts +43 -3
- package/src/self-modifying-agent.ts +132 -13
- package/src/tool-generation/generated-tool-store.ts +2 -4
- package/src/tool-generation/prompts.ts +26 -21
- package/src/tool-generation/tool-generator.ts +13 -15
- package/src/tool-generation/tool-sandbox.ts +12 -2
- package/src/tool-generation/tool-validator.ts +0 -5
- package/src/utils/index.ts +40 -0
- package/src/utils/llm-helper.ts +2 -2
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { SelfModifyingEventEmitter } from '../events';
|
|
3
|
+
import type { SelfModifyingEvent } from '@cogitator-ai/types';
|
|
4
|
+
|
|
5
|
+
function makeEvent(type: string): SelfModifyingEvent {
|
|
6
|
+
return { type, runId: 'test', timestamp: new Date(), data: {} } as SelfModifyingEvent;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe('SelfModifyingEventEmitter', () => {
|
|
10
|
+
it('emits to specific type handlers', async () => {
|
|
11
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
12
|
+
const handler = vi.fn();
|
|
13
|
+
|
|
14
|
+
emitter.on('run_started', handler);
|
|
15
|
+
await emitter.emit(makeEvent('run_started'));
|
|
16
|
+
|
|
17
|
+
expect(handler).toHaveBeenCalledOnce();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('emits to wildcard handlers', async () => {
|
|
21
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
22
|
+
const handler = vi.fn();
|
|
23
|
+
|
|
24
|
+
emitter.on('*', handler);
|
|
25
|
+
await emitter.emit(makeEvent('run_started'));
|
|
26
|
+
await emitter.emit(makeEvent('run_completed'));
|
|
27
|
+
|
|
28
|
+
expect(handler).toHaveBeenCalledTimes(2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('does not call handlers for different event types', async () => {
|
|
32
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
33
|
+
const handler = vi.fn();
|
|
34
|
+
|
|
35
|
+
emitter.on('run_started', handler);
|
|
36
|
+
await emitter.emit(makeEvent('run_completed'));
|
|
37
|
+
|
|
38
|
+
expect(handler).not.toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('removes handler with off()', async () => {
|
|
42
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
43
|
+
const handler = vi.fn();
|
|
44
|
+
|
|
45
|
+
emitter.on('run_started', handler);
|
|
46
|
+
emitter.off('run_started', handler);
|
|
47
|
+
await emitter.emit(makeEvent('run_started'));
|
|
48
|
+
|
|
49
|
+
expect(handler).not.toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('returns unsubscribe function from on()', async () => {
|
|
53
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
54
|
+
const handler = vi.fn();
|
|
55
|
+
|
|
56
|
+
const unsub = emitter.on('run_started', handler);
|
|
57
|
+
unsub();
|
|
58
|
+
await emitter.emit(makeEvent('run_started'));
|
|
59
|
+
|
|
60
|
+
expect(handler).not.toHaveBeenCalled();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('survives handler errors with Promise.allSettled', async () => {
|
|
64
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
65
|
+
const goodHandler = vi.fn();
|
|
66
|
+
const badHandler = vi.fn(() => {
|
|
67
|
+
throw new Error('boom');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
emitter.on('run_started', badHandler);
|
|
71
|
+
emitter.on('run_started', goodHandler);
|
|
72
|
+
|
|
73
|
+
await emitter.emit(makeEvent('run_started'));
|
|
74
|
+
|
|
75
|
+
expect(badHandler).toHaveBeenCalled();
|
|
76
|
+
expect(goodHandler).toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('removeAllListeners clears specific event', () => {
|
|
80
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
81
|
+
emitter.on('run_started', vi.fn());
|
|
82
|
+
emitter.on('run_completed', vi.fn());
|
|
83
|
+
|
|
84
|
+
emitter.removeAllListeners('run_started');
|
|
85
|
+
|
|
86
|
+
expect(emitter.listenerCount('run_started')).toBe(0);
|
|
87
|
+
expect(emitter.listenerCount('run_completed')).toBe(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('removeAllListeners clears all events', () => {
|
|
91
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
92
|
+
emitter.on('run_started', vi.fn());
|
|
93
|
+
emitter.on('run_completed', vi.fn());
|
|
94
|
+
emitter.on('*', vi.fn());
|
|
95
|
+
|
|
96
|
+
emitter.removeAllListeners();
|
|
97
|
+
|
|
98
|
+
expect(emitter.listenerCount('run_started')).toBe(0);
|
|
99
|
+
expect(emitter.listenerCount('run_completed')).toBe(0);
|
|
100
|
+
expect(emitter.listenerCount('*')).toBe(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('listenerCount returns correct count', () => {
|
|
104
|
+
const emitter = new SelfModifyingEventEmitter();
|
|
105
|
+
expect(emitter.listenerCount('run_started')).toBe(0);
|
|
106
|
+
|
|
107
|
+
emitter.on('run_started', vi.fn());
|
|
108
|
+
emitter.on('run_started', vi.fn());
|
|
109
|
+
|
|
110
|
+
expect(emitter.listenerCount('run_started')).toBe(2);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { SelfModifyingAgent } from '../self-modifying-agent';
|
|
3
|
+
import type { Agent, LLMBackend, Tool, ChatResponse, ToolCall } from '@cogitator-ai/types';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
6
|
+
function createMockAgent(tools: Tool[] = []): Agent {
|
|
7
|
+
return {
|
|
8
|
+
name: 'test-agent',
|
|
9
|
+
model: 'test-model',
|
|
10
|
+
instructions: 'You are a test assistant.',
|
|
11
|
+
tools,
|
|
12
|
+
config: { temperature: 0.5 },
|
|
13
|
+
} as Agent;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function createMockTool(name: string, result: unknown): Tool {
|
|
17
|
+
return {
|
|
18
|
+
name,
|
|
19
|
+
description: `Mock tool: ${name}`,
|
|
20
|
+
parameters: z.object({ input: z.string() }),
|
|
21
|
+
execute: vi.fn().mockResolvedValue(result),
|
|
22
|
+
toJSON: () => ({
|
|
23
|
+
name,
|
|
24
|
+
description: `Mock tool: ${name}`,
|
|
25
|
+
parameters: { type: 'object' as const, properties: { input: { type: 'string' } } },
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function chatResponse(content: string, toolCalls?: ToolCall[]): ChatResponse {
|
|
31
|
+
return {
|
|
32
|
+
content,
|
|
33
|
+
toolCalls,
|
|
34
|
+
finishReason: toolCalls?.length ? 'tool_calls' : 'stop',
|
|
35
|
+
usage: { inputTokens: 10, outputTokens: 10, totalTokens: 20 },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('SelfModifyingAgent tool execution', () => {
|
|
40
|
+
it('passes tools to LLM and returns response', async () => {
|
|
41
|
+
const calculator = createMockTool('calculator', { result: 42 });
|
|
42
|
+
|
|
43
|
+
const mockLLM: Partial<LLMBackend> = {
|
|
44
|
+
chat: vi.fn()
|
|
45
|
+
.mockResolvedValueOnce(chatResponse('', [
|
|
46
|
+
{ id: 'call_1', name: 'calculator', arguments: { input: '6*7' } },
|
|
47
|
+
]))
|
|
48
|
+
.mockResolvedValueOnce(chatResponse('The answer is 42.')),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const agent = createMockAgent([calculator]);
|
|
52
|
+
const selfMod = new SelfModifyingAgent({
|
|
53
|
+
agent,
|
|
54
|
+
llm: mockLLM as LLMBackend,
|
|
55
|
+
config: {
|
|
56
|
+
toolGeneration: { enabled: false },
|
|
57
|
+
metaReasoning: { enabled: false },
|
|
58
|
+
architectureEvolution: { enabled: false },
|
|
59
|
+
constraints: { enabled: false },
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const result = await selfMod.run('What is 6 times 7?');
|
|
64
|
+
|
|
65
|
+
expect(result.output).toBe('The answer is 42.');
|
|
66
|
+
expect(calculator.execute).toHaveBeenCalledOnce();
|
|
67
|
+
expect(calculator.execute).toHaveBeenCalledWith(
|
|
68
|
+
{ input: '6*7' },
|
|
69
|
+
expect.objectContaining({ agentId: 'test-agent' }),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const chatCalls = vi.mocked(mockLLM.chat!).mock.calls;
|
|
73
|
+
expect(chatCalls[0][0].tools).toBeDefined();
|
|
74
|
+
expect(chatCalls[0][0].tools).toHaveLength(1);
|
|
75
|
+
expect(chatCalls[0][0].tools![0].name).toBe('calculator');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('handles multiple sequential tool calls', async () => {
|
|
79
|
+
const search = createMockTool('search', { results: ['found it'] });
|
|
80
|
+
const format = createMockTool('format', { text: 'formatted result' });
|
|
81
|
+
|
|
82
|
+
const mockLLM: Partial<LLMBackend> = {
|
|
83
|
+
chat: vi.fn()
|
|
84
|
+
.mockResolvedValueOnce(chatResponse('', [
|
|
85
|
+
{ id: 'call_1', name: 'search', arguments: { input: 'test' } },
|
|
86
|
+
]))
|
|
87
|
+
.mockResolvedValueOnce(chatResponse('', [
|
|
88
|
+
{ id: 'call_2', name: 'format', arguments: { input: 'found it' } },
|
|
89
|
+
]))
|
|
90
|
+
.mockResolvedValueOnce(chatResponse('Here is your formatted result.')),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const agent = createMockAgent([search, format]);
|
|
94
|
+
const selfMod = new SelfModifyingAgent({
|
|
95
|
+
agent,
|
|
96
|
+
llm: mockLLM as LLMBackend,
|
|
97
|
+
config: {
|
|
98
|
+
toolGeneration: { enabled: false },
|
|
99
|
+
metaReasoning: { enabled: false },
|
|
100
|
+
architectureEvolution: { enabled: false },
|
|
101
|
+
constraints: { enabled: false },
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const result = await selfMod.run('Search and format');
|
|
106
|
+
|
|
107
|
+
expect(result.output).toBe('Here is your formatted result.');
|
|
108
|
+
expect(search.execute).toHaveBeenCalledOnce();
|
|
109
|
+
expect(format.execute).toHaveBeenCalledOnce();
|
|
110
|
+
expect(vi.mocked(mockLLM.chat!)).toHaveBeenCalledTimes(3);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('handles tool execution errors gracefully', async () => {
|
|
114
|
+
const failTool = createMockTool('broken', null);
|
|
115
|
+
(failTool.execute as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('tool crashed'));
|
|
116
|
+
|
|
117
|
+
const mockLLM: Partial<LLMBackend> = {
|
|
118
|
+
chat: vi.fn()
|
|
119
|
+
.mockResolvedValueOnce(chatResponse('', [
|
|
120
|
+
{ id: 'call_1', name: 'broken', arguments: { input: 'test' } },
|
|
121
|
+
]))
|
|
122
|
+
.mockResolvedValueOnce(chatResponse('The tool failed but I can help anyway.')),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const agent = createMockAgent([failTool]);
|
|
126
|
+
const selfMod = new SelfModifyingAgent({
|
|
127
|
+
agent,
|
|
128
|
+
llm: mockLLM as LLMBackend,
|
|
129
|
+
config: {
|
|
130
|
+
toolGeneration: { enabled: false },
|
|
131
|
+
metaReasoning: { enabled: false },
|
|
132
|
+
architectureEvolution: { enabled: false },
|
|
133
|
+
constraints: { enabled: false },
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = await selfMod.run('Use the broken tool');
|
|
138
|
+
|
|
139
|
+
expect(result.output).toBe('The tool failed but I can help anyway.');
|
|
140
|
+
const secondCall = vi.mocked(mockLLM.chat!).mock.calls[1][0];
|
|
141
|
+
const toolMsg = secondCall.messages.find((m: { role: string }) => m.role === 'tool');
|
|
142
|
+
expect(toolMsg?.content).toContain('tool crashed');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('handles unknown tool calls', async () => {
|
|
146
|
+
const mockLLM: Partial<LLMBackend> = {
|
|
147
|
+
chat: vi.fn()
|
|
148
|
+
.mockResolvedValueOnce(chatResponse('', [
|
|
149
|
+
{ id: 'call_1', name: 'nonexistent', arguments: { input: 'x' } },
|
|
150
|
+
]))
|
|
151
|
+
.mockResolvedValueOnce(chatResponse('I could not find that tool.')),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const agent = createMockAgent([]);
|
|
155
|
+
const selfMod = new SelfModifyingAgent({
|
|
156
|
+
agent,
|
|
157
|
+
llm: mockLLM as LLMBackend,
|
|
158
|
+
config: {
|
|
159
|
+
toolGeneration: { enabled: false },
|
|
160
|
+
metaReasoning: { enabled: false },
|
|
161
|
+
architectureEvolution: { enabled: false },
|
|
162
|
+
constraints: { enabled: false },
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const result = await selfMod.run('Call a tool');
|
|
167
|
+
|
|
168
|
+
expect(result.output).toBe('I could not find that tool.');
|
|
169
|
+
const secondCall = vi.mocked(mockLLM.chat!).mock.calls[1][0];
|
|
170
|
+
const toolMsg = secondCall.messages.find((m: { role: string }) => m.role === 'tool');
|
|
171
|
+
expect(toolMsg?.content).toContain('not found');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('works without any tools (no tools passed to LLM)', async () => {
|
|
175
|
+
const mockLLM: Partial<LLMBackend> = {
|
|
176
|
+
chat: vi.fn().mockResolvedValueOnce(chatResponse('Hello world')),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const agent = createMockAgent([]);
|
|
180
|
+
const selfMod = new SelfModifyingAgent({
|
|
181
|
+
agent,
|
|
182
|
+
llm: mockLLM as LLMBackend,
|
|
183
|
+
config: {
|
|
184
|
+
toolGeneration: { enabled: false },
|
|
185
|
+
metaReasoning: { enabled: false },
|
|
186
|
+
architectureEvolution: { enabled: false },
|
|
187
|
+
constraints: { enabled: false },
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const result = await selfMod.run('Just say hi');
|
|
192
|
+
|
|
193
|
+
expect(result.output).toBe('Hello world');
|
|
194
|
+
const chatCall = vi.mocked(mockLLM.chat!).mock.calls[0][0];
|
|
195
|
+
expect(chatCall.tools).toBeUndefined();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { extractJson, llmChat } from '../utils';
|
|
3
|
+
|
|
4
|
+
describe('extractJson', () => {
|
|
5
|
+
it('extracts simple JSON object', () => {
|
|
6
|
+
const result = extractJson('{"key": "value"}');
|
|
7
|
+
expect(result).toBe('{"key": "value"}');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('extracts JSON from surrounding text', () => {
|
|
11
|
+
const result = extractJson('Here is the result: {"onTrack": true, "confidence": 0.8} and that is it.');
|
|
12
|
+
expect(result).toBe('{"onTrack": true, "confidence": 0.8}');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('handles nested braces', () => {
|
|
16
|
+
const result = extractJson('{"a": {"b": {"c": 1}}}');
|
|
17
|
+
expect(JSON.parse(result!)).toEqual({ a: { b: { c: 1 } } });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('ignores braces inside strings', () => {
|
|
21
|
+
const result = extractJson('{"text": "hello {world}"}');
|
|
22
|
+
expect(JSON.parse(result!)).toEqual({ text: 'hello {world}' });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('handles escaped quotes in strings', () => {
|
|
26
|
+
const result = extractJson('{"text": "she said \\"hi\\""}');
|
|
27
|
+
expect(result).toBe('{"text": "she said \\"hi\\""}');
|
|
28
|
+
expect(JSON.parse(result!)).toEqual({ text: 'she said "hi"' });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('returns null for no JSON', () => {
|
|
32
|
+
expect(extractJson('no json here')).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('returns null for empty input', () => {
|
|
36
|
+
expect(extractJson('')).toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('does not greedily match across multiple JSON objects', () => {
|
|
40
|
+
const input = 'Note: {not valid} But here is: {"valid": true}';
|
|
41
|
+
const result = extractJson(input);
|
|
42
|
+
expect(result).toBe('{not valid}');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('handles first valid JSON from LLM response with preamble containing braces', () => {
|
|
46
|
+
const input = `Here's my analysis:
|
|
47
|
+
{"hasGap": true, "gaps": []}`;
|
|
48
|
+
const result = extractJson(input);
|
|
49
|
+
expect(JSON.parse(result!)).toEqual({ hasGap: true, gaps: [] });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('handles arrays inside objects', () => {
|
|
53
|
+
const result = extractJson('{"items": [1, 2, {"nested": true}]}');
|
|
54
|
+
expect(JSON.parse(result!)).toEqual({ items: [1, 2, { nested: true }] });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('llmChat', () => {
|
|
59
|
+
it('uses complete when available', async () => {
|
|
60
|
+
const llm = {
|
|
61
|
+
complete: async (_opts: { messages: unknown[] }) => ({
|
|
62
|
+
content: `response from complete`,
|
|
63
|
+
usage: { inputTokens: 10, outputTokens: 20 },
|
|
64
|
+
}),
|
|
65
|
+
chat: async () => ({
|
|
66
|
+
content: 'should not be called',
|
|
67
|
+
usage: { inputTokens: 10, outputTokens: 20 },
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const result = await llmChat(llm as never, [{ role: 'user', content: 'hi' }]);
|
|
72
|
+
expect(result).toBe('response from complete');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('falls back to chat when complete is unavailable', async () => {
|
|
76
|
+
const llm = {
|
|
77
|
+
chat: async (opts: { model: string }) => ({
|
|
78
|
+
content: `response from chat with model ${opts.model}`,
|
|
79
|
+
usage: { inputTokens: 10, outputTokens: 20 },
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const result = await llmChat(llm as never, [{ role: 'user', content: 'hi' }], {
|
|
84
|
+
model: 'gpt-4',
|
|
85
|
+
});
|
|
86
|
+
expect(result).toBe('response from chat with model gpt-4');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('uses default model when none specified', async () => {
|
|
90
|
+
const llm = {
|
|
91
|
+
chat: async (opts: { model: string }) => ({
|
|
92
|
+
content: opts.model,
|
|
93
|
+
usage: { inputTokens: 10, outputTokens: 20 },
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const result = await llmChat(llm as never, [{ role: 'user', content: 'hi' }]);
|
|
98
|
+
expect(result).toBe('default');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -151,7 +151,7 @@ export class CapabilityAnalyzer {
|
|
|
151
151
|
let profile: TaskProfile;
|
|
152
152
|
|
|
153
153
|
if (this.llm && this.enableLLMAnalysis) {
|
|
154
|
-
profile = await this.
|
|
154
|
+
profile = await this.analyzeWithLLM(taskDescription, context);
|
|
155
155
|
} else {
|
|
156
156
|
profile = this.analyzeHeuristically(taskDescription, context?.availableTools);
|
|
157
157
|
}
|
|
@@ -167,7 +167,7 @@ export class CapabilityAnalyzer {
|
|
|
167
167
|
return profile;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
private async
|
|
170
|
+
private async analyzeWithLLM(
|
|
171
171
|
taskDescription: string,
|
|
172
172
|
context?: {
|
|
173
173
|
availableTools?: Tool[];
|
|
@@ -266,9 +266,11 @@ export class CapabilityAnalyzer {
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
private detectComplexity(task: string, wordCount: number): TaskProfile['complexity'] {
|
|
269
|
+
const taskWords = new Set(task.split(/\s+/));
|
|
270
|
+
|
|
269
271
|
for (const [level, indicators] of Object.entries(COMPLEXITY_INDICATORS).reverse()) {
|
|
270
272
|
for (const indicator of indicators) {
|
|
271
|
-
if (
|
|
273
|
+
if (taskWords.has(indicator)) {
|
|
272
274
|
return level as TaskProfile['complexity'];
|
|
273
275
|
}
|
|
274
276
|
}
|
|
@@ -176,17 +176,18 @@ export class EvolutionStrategy {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
private sampleNormal(): number {
|
|
179
|
-
const u1 = Math.random();
|
|
179
|
+
const u1 = Math.random() || Number.MIN_VALUE;
|
|
180
180
|
const u2 = Math.random();
|
|
181
181
|
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
updateCandidate(candidate: EvolutionCandidate, reward: number): void {
|
|
185
|
+
const clampedReward = Math.max(0, Math.min(1, reward));
|
|
185
186
|
const oldScore = candidate.score;
|
|
186
187
|
const oldCount = candidate.evaluationCount;
|
|
187
188
|
|
|
188
189
|
candidate.evaluationCount++;
|
|
189
|
-
candidate.score = (oldScore * oldCount +
|
|
190
|
+
candidate.score = (oldScore * oldCount + clampedReward) / candidate.evaluationCount;
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
getExplorationRate(): number {
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
EvolutionMetrics,
|
|
8
8
|
} from '@cogitator-ai/types';
|
|
9
9
|
import { CapabilityAnalyzer } from './capability-analyzer';
|
|
10
|
-
import { EvolutionStrategy
|
|
10
|
+
import { EvolutionStrategy } from './evolution-strategy';
|
|
11
11
|
import {
|
|
12
12
|
buildCandidateGenerationPrompt,
|
|
13
13
|
buildPerformanceAnalysisPrompt,
|
|
@@ -48,6 +48,7 @@ export class ParameterOptimizer {
|
|
|
48
48
|
private candidates: EvolutionCandidate[] = [];
|
|
49
49
|
private history: HistoricalRecord[] = [];
|
|
50
50
|
private currentGeneration = 0;
|
|
51
|
+
private lastEvolutionTimestamp = 0;
|
|
51
52
|
private readonly maxHistorySize = 100;
|
|
52
53
|
|
|
53
54
|
constructor(options: ParameterOptimizerOptions) {
|
|
@@ -173,6 +174,7 @@ export class ParameterOptimizer {
|
|
|
173
174
|
|
|
174
175
|
private async evolve(currentProfile: TaskProfile): Promise<void> {
|
|
175
176
|
this.currentGeneration++;
|
|
177
|
+
this.lastEvolutionTimestamp = Date.now();
|
|
176
178
|
|
|
177
179
|
const topCandidates = [...this.candidates]
|
|
178
180
|
.filter((c) => c.evaluationCount > 0)
|
|
@@ -325,10 +327,7 @@ export class ParameterOptimizer {
|
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
private getLastEvolutionTime(): number {
|
|
328
|
-
|
|
329
|
-
(c) => c.generation === this.currentGeneration && c.evaluationCount === 0
|
|
330
|
-
);
|
|
331
|
-
return latestGenCandidate ? Date.now() - 300000 : 0;
|
|
330
|
+
return this.lastEvolutionTimestamp;
|
|
332
331
|
}
|
|
333
332
|
|
|
334
333
|
private getRelevantHistory(profile: TaskProfile): HistoricalRecord[] {
|
|
@@ -410,7 +409,7 @@ export class ParameterOptimizer {
|
|
|
410
409
|
fields++;
|
|
411
410
|
}
|
|
412
411
|
|
|
413
|
-
if (a.model !== b.model) {
|
|
412
|
+
if (a.model !== undefined && b.model !== undefined && a.model !== b.model) {
|
|
414
413
|
distance += 1;
|
|
415
414
|
fields++;
|
|
416
415
|
}
|
|
@@ -447,8 +446,9 @@ export class ParameterOptimizer {
|
|
|
447
446
|
if (evaluatedCandidates.length === 0) return null;
|
|
448
447
|
|
|
449
448
|
const results = evaluatedCandidates.map((c) => {
|
|
449
|
+
const configKey = JSON.stringify(c.config, Object.keys(c.config).sort());
|
|
450
450
|
const records = this.history.filter(
|
|
451
|
-
(h) => JSON.stringify(h.config
|
|
451
|
+
(h) => JSON.stringify(h.config, Object.keys(h.config).sort()) === configKey
|
|
452
452
|
);
|
|
453
453
|
const avgMetrics =
|
|
454
454
|
records.length > 0
|
|
@@ -503,6 +503,7 @@ export class ParameterOptimizer {
|
|
|
503
503
|
this.candidates = [];
|
|
504
504
|
this.history = [];
|
|
505
505
|
this.currentGeneration = 0;
|
|
506
|
+
this.lastEvolutionTimestamp = 0;
|
|
506
507
|
this.evolutionStrategy.reset();
|
|
507
508
|
}
|
|
508
509
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TaskProfile, ArchitectureConfig, EvolutionCandidate } from '@cogitator-ai/types';
|
|
2
|
+
import { extractJson } from '../utils';
|
|
2
3
|
|
|
3
4
|
export const ARCHITECTURE_ANALYSIS_SYSTEM_PROMPT = `You are an expert in AI agent architecture optimization.
|
|
4
5
|
Your task is to analyze tasks and recommend optimal configurations.
|
|
@@ -140,11 +141,11 @@ Respond with:
|
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
export function parseTaskProfileResponse(response: string): TaskProfile | null {
|
|
143
|
-
const
|
|
144
|
-
if (!
|
|
144
|
+
const json = extractJson(response);
|
|
145
|
+
if (!json) return null;
|
|
145
146
|
|
|
146
147
|
try {
|
|
147
|
-
const parsed = JSON.parse(
|
|
148
|
+
const parsed = JSON.parse(json);
|
|
148
149
|
|
|
149
150
|
return {
|
|
150
151
|
complexity: parsed.complexity || 'moderate',
|
|
@@ -197,11 +198,11 @@ export function parsePerformanceAnalysisResponse(response: string): {
|
|
|
197
198
|
shouldAdopt: boolean;
|
|
198
199
|
analysis: string;
|
|
199
200
|
} | null {
|
|
200
|
-
const
|
|
201
|
-
if (!
|
|
201
|
+
const json = extractJson(response);
|
|
202
|
+
if (!json) return null;
|
|
202
203
|
|
|
203
204
|
try {
|
|
204
|
-
const parsed = JSON.parse(
|
|
205
|
+
const parsed = JSON.parse(json);
|
|
205
206
|
|
|
206
207
|
return {
|
|
207
208
|
recommendation: String(parsed.recommendation || ''),
|
package/src/constraints/index.ts
CHANGED
|
@@ -92,23 +92,7 @@ export class ModificationValidator {
|
|
|
92
92
|
return this.evaluateExpression(rule, request.payload as Record<string, unknown>);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
switch (rule.type) {
|
|
98
|
-
case 'invariant':
|
|
99
|
-
return this.evaluateExpression(rule.expression ?? '', payload);
|
|
100
|
-
case 'precondition':
|
|
101
|
-
return this.evaluateExpression(rule.expression ?? '', payload);
|
|
102
|
-
case 'postcondition':
|
|
103
|
-
return true;
|
|
104
|
-
case 'temporal':
|
|
105
|
-
if (rule.pattern?.source === 'never') {
|
|
106
|
-
return !this.evaluateExpression(rule.expression ?? '', payload);
|
|
107
|
-
}
|
|
108
|
-
return true;
|
|
109
|
-
default:
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
95
|
+
return this.evaluateExpression(rule.expression ?? '', request.payload as Record<string, unknown>);
|
|
112
96
|
}
|
|
113
97
|
|
|
114
98
|
private evaluateExpression(expression: string, context: Record<string, unknown>): boolean {
|
|
@@ -127,16 +111,18 @@ export class ModificationValidator {
|
|
|
127
111
|
|
|
128
112
|
if (conditions.length === 0) return true;
|
|
129
113
|
|
|
130
|
-
|
|
114
|
+
const orGroups: boolean[][] = [[]];
|
|
115
|
+
orGroups[0].push(conditions[0]);
|
|
116
|
+
|
|
131
117
|
for (let i = 0; i < operators.length; i++) {
|
|
132
118
|
if (operators[i] === 'AND') {
|
|
133
|
-
|
|
119
|
+
orGroups[orGroups.length - 1].push(conditions[i + 1]);
|
|
134
120
|
} else {
|
|
135
|
-
|
|
121
|
+
orGroups.push([conditions[i + 1]]);
|
|
136
122
|
}
|
|
137
123
|
}
|
|
138
124
|
|
|
139
|
-
return
|
|
125
|
+
return orGroups.some((group) => group.every(Boolean));
|
|
140
126
|
}
|
|
141
127
|
|
|
142
128
|
private evaluateSimpleCondition(condition: string, context: Record<string, unknown>): boolean {
|
|
@@ -384,6 +370,11 @@ export class ModificationValidator {
|
|
|
384
370
|
}
|
|
385
371
|
|
|
386
372
|
getConstraints(): ModificationConstraints {
|
|
387
|
-
return {
|
|
373
|
+
return {
|
|
374
|
+
safety: [...this.constraints.safety],
|
|
375
|
+
capability: [...this.constraints.capability],
|
|
376
|
+
resource: [...this.constraints.resource],
|
|
377
|
+
custom: this.constraints.custom ? [...this.constraints.custom] : [],
|
|
378
|
+
};
|
|
388
379
|
}
|
|
389
380
|
}
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
SafetyConstraint,
|
|
3
3
|
CapabilityConstraint,
|
|
4
4
|
ResourceConstraint,
|
|
5
|
+
CustomConstraint,
|
|
5
6
|
ModificationConstraints,
|
|
6
7
|
} from '@cogitator-ai/types';
|
|
7
8
|
import { DEFAULT_SAFETY_CONSTRAINTS } from '@cogitator-ai/types';
|
|
@@ -84,6 +85,20 @@ export function mergeResourceConstraints(
|
|
|
84
85
|
return [...result.values()];
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
export function mergeCustomConstraints(
|
|
89
|
+
base: CustomConstraint[],
|
|
90
|
+
additions: CustomConstraint[]
|
|
91
|
+
): CustomConstraint[] {
|
|
92
|
+
const result = new Map<string, CustomConstraint>();
|
|
93
|
+
for (const c of base) {
|
|
94
|
+
result.set(c.id, c);
|
|
95
|
+
}
|
|
96
|
+
for (const c of additions) {
|
|
97
|
+
result.set(c.id, c);
|
|
98
|
+
}
|
|
99
|
+
return [...result.values()];
|
|
100
|
+
}
|
|
101
|
+
|
|
87
102
|
export function mergeConstraints(
|
|
88
103
|
base: ModificationConstraints,
|
|
89
104
|
additions: Partial<ModificationConstraints>
|
|
@@ -96,6 +111,6 @@ export function mergeConstraints(
|
|
|
96
111
|
resource: additions.resource
|
|
97
112
|
? mergeResourceConstraints(base.resource, additions.resource)
|
|
98
113
|
: base.resource,
|
|
99
|
-
custom:
|
|
114
|
+
custom: mergeCustomConstraints(base.custom ?? [], additions.custom ?? []),
|
|
100
115
|
};
|
|
101
116
|
}
|
|
@@ -30,7 +30,15 @@ export class SelfModifyingEventEmitter {
|
|
|
30
30
|
...(wildcardHandlers ? [...wildcardHandlers] : []),
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
-
await Promise.
|
|
33
|
+
await Promise.allSettled(
|
|
34
|
+
allHandlers.map((h) => {
|
|
35
|
+
try {
|
|
36
|
+
return Promise.resolve(h(event));
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return Promise.reject(e instanceof Error ? e : new Error(String(e)));
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
);
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
createEvent(
|