@far-world-labs/verblets 0.1.4 → 0.2.0
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/ci.yml +38 -43
- package/.vitest.config.examples.js +4 -0
- package/DEVELOPING.md +1 -1
- package/package.json +9 -9
- package/scripts/clear-redis.js +74 -0
- package/src/chains/conversation/README.md +26 -0
- package/src/chains/conversation/index.examples.js +398 -0
- package/src/chains/conversation/index.js +126 -0
- package/src/chains/conversation/index.spec.js +148 -0
- package/src/chains/conversation/turn-policies.js +93 -0
- package/src/chains/conversation/turn-policies.md +123 -0
- package/src/chains/conversation/turn-policies.spec.js +135 -0
- package/src/chains/expect/index.js +34 -0
- package/src/chains/intersections/README.md +20 -6
- package/src/chains/intersections/index.examples.js +9 -8
- package/src/chains/intersections/index.js +39 -187
- package/src/chains/llm-logger/README.md +291 -133
- package/src/chains/llm-logger/index.js +451 -65
- package/src/chains/llm-logger/index.spec.js +85 -24
- package/src/chains/llm-logger/schema.json +105 -0
- package/src/chains/set-interval/index.examples.js +34 -6
- package/src/chains/set-interval/index.js +53 -32
- package/src/chains/themes/index.js +2 -2
- package/src/constants/common.js +7 -1
- package/src/constants/models.js +21 -9
- package/src/index.js +14 -4
- package/src/lib/assert/README.md +84 -0
- package/src/lib/assert/index.js +50 -0
- package/src/lib/ring-buffer/README.md +50 -428
- package/src/lib/ring-buffer/index.js +148 -987
- package/src/lib/ring-buffer/index.spec.js +388 -0
- package/src/verblets/conversation-turn/README.md +33 -0
- package/src/verblets/conversation-turn/index.examples.js +218 -0
- package/src/verblets/conversation-turn/index.js +68 -0
- package/src/verblets/conversation-turn/index.spec.js +77 -0
- package/src/verblets/conversation-turn-multi/README.md +31 -0
- package/src/verblets/conversation-turn-multi/index.examples.js +160 -0
- package/src/verblets/conversation-turn-multi/index.js +104 -0
- package/src/verblets/conversation-turn-multi/index.spec.js +63 -0
- package/src/verblets/intent/index.examples.js +1 -1
- package/src/verblets/intersection/index.js +46 -5
- package/src/verblets/people-list/README.md +28 -0
- package/src/verblets/people-list/index.examples.js +184 -0
- package/src/verblets/people-list/index.js +44 -0
- package/src/verblets/people-list/index.spec.js +49 -0
- package/scripts/version-bump.js +0 -33
package/.github/workflows/ci.yml
CHANGED
|
@@ -72,50 +72,11 @@ jobs:
|
|
|
72
72
|
echo "✅ ESLint checks completed"
|
|
73
73
|
echo "✅ Library is ready for deployment"
|
|
74
74
|
|
|
75
|
-
# Version bump for PRs - creates commit in PR branch
|
|
76
|
-
version-bump:
|
|
77
|
-
name: 📦 Version Bump
|
|
78
|
-
runs-on: ubuntu-latest
|
|
79
|
-
needs: build
|
|
80
|
-
if: github.event_name == 'pull_request'
|
|
81
|
-
permissions:
|
|
82
|
-
contents: write
|
|
83
|
-
pull-requests: write
|
|
84
|
-
steps:
|
|
85
|
-
- uses: actions/checkout@v4
|
|
86
|
-
with:
|
|
87
|
-
fetch-depth: 0
|
|
88
|
-
token: ${{ secrets.GITHUB_TOKEN }}
|
|
89
|
-
ref: ${{ github.head_ref }}
|
|
90
|
-
- uses: actions/setup-node@v4
|
|
91
|
-
with:
|
|
92
|
-
node-version: 20.x
|
|
93
|
-
cache: 'npm'
|
|
94
|
-
- run: npm ci
|
|
95
|
-
|
|
96
|
-
- name: Configure Git
|
|
97
|
-
run: |
|
|
98
|
-
git config --global user.name "github-actions[bot]"
|
|
99
|
-
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
100
|
-
|
|
101
|
-
- name: Version Bump (No Publish)
|
|
102
|
-
env:
|
|
103
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
104
|
-
run: |
|
|
105
|
-
# Ensure we're on the correct branch
|
|
106
|
-
git checkout ${{ github.head_ref }}
|
|
107
|
-
|
|
108
|
-
# Use release-it to bump version and create tag, but skip npm publish
|
|
109
|
-
npx release-it --ci --no-npm.publish --no-github.release
|
|
110
|
-
|
|
111
|
-
# Push the version bump back to the PR branch
|
|
112
|
-
git push origin ${{ github.head_ref }}
|
|
113
|
-
|
|
114
75
|
# Required status check that gates merge operations
|
|
115
76
|
pr-ready-to-merge:
|
|
116
77
|
name: ✅ PR Ready to Merge
|
|
117
78
|
runs-on: ubuntu-latest
|
|
118
|
-
needs: [lint, test, build
|
|
79
|
+
needs: [lint, test, build]
|
|
119
80
|
if: github.event_name == 'pull_request'
|
|
120
81
|
steps:
|
|
121
82
|
- name: All checks passed
|
|
@@ -124,11 +85,11 @@ jobs:
|
|
|
124
85
|
echo "✅ Linting: Passed"
|
|
125
86
|
echo "✅ Tests: Passed on all LTS Node versions"
|
|
126
87
|
echo "✅ Build: Successful"
|
|
127
|
-
echo "
|
|
88
|
+
echo "ℹ️ Version: Will be checked on merge (publish only if bumped)"
|
|
128
89
|
echo ""
|
|
129
90
|
echo "This PR is now ready for squash and merge."
|
|
130
91
|
|
|
131
|
-
# Publish
|
|
92
|
+
# Publish to NPM and create git tag - only if version was bumped
|
|
132
93
|
release:
|
|
133
94
|
name: 🚀 Publish to NPM
|
|
134
95
|
runs-on: ubuntu-latest
|
|
@@ -149,14 +110,42 @@ jobs:
|
|
|
149
110
|
cache: 'npm'
|
|
150
111
|
- run: npm ci
|
|
151
112
|
|
|
113
|
+
- name: Check if version was bumped
|
|
114
|
+
id: version-check
|
|
115
|
+
run: |
|
|
116
|
+
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
|
117
|
+
PUBLISHED_VERSION=$(npm view @far-world-labs/verblets version 2>/dev/null || echo "0.0.0")
|
|
118
|
+
|
|
119
|
+
echo "📦 Current version in package.json: $CURRENT_VERSION"
|
|
120
|
+
echo "📦 Published version on npm: $PUBLISHED_VERSION"
|
|
121
|
+
|
|
122
|
+
if [ "$CURRENT_VERSION" = "$PUBLISHED_VERSION" ]; then
|
|
123
|
+
echo "should-publish=false" >> $GITHUB_OUTPUT
|
|
124
|
+
echo "ℹ️ No version bump detected - skipping publish"
|
|
125
|
+
else
|
|
126
|
+
echo "should-publish=true" >> $GITHUB_OUTPUT
|
|
127
|
+
echo "✅ Version bump detected: $PUBLISHED_VERSION → $CURRENT_VERSION - will publish"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
- name: Create Git Tag
|
|
131
|
+
if: steps.version-check.outputs.should-publish == 'true'
|
|
132
|
+
run: |
|
|
133
|
+
VERSION=$(node -p "require('./package.json').version")
|
|
134
|
+
git config --global user.name "github-actions[bot]"
|
|
135
|
+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
136
|
+
git tag -a "v$VERSION" -m "Release v$VERSION"
|
|
137
|
+
git push origin "v$VERSION"
|
|
138
|
+
|
|
152
139
|
- name: Publish to NPM
|
|
140
|
+
if: steps.version-check.outputs.should-publish == 'true'
|
|
153
141
|
env:
|
|
154
142
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
155
143
|
run: |
|
|
156
|
-
#
|
|
144
|
+
# Version was manually bumped and validated in PR
|
|
157
145
|
npm publish --access public
|
|
158
146
|
|
|
159
147
|
- name: Create GitHub Release
|
|
148
|
+
if: steps.version-check.outputs.should-publish == 'true'
|
|
160
149
|
env:
|
|
161
150
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
162
151
|
run: |
|
|
@@ -168,3 +157,9 @@ jobs:
|
|
|
168
157
|
--title "Release v$VERSION" \
|
|
169
158
|
--notes "Automated release of version $VERSION" \
|
|
170
159
|
--latest
|
|
160
|
+
|
|
161
|
+
- name: Skip publish
|
|
162
|
+
if: steps.version-check.outputs.should-publish == 'false'
|
|
163
|
+
run: |
|
|
164
|
+
echo "ℹ️ No version bump detected - publish skipped"
|
|
165
|
+
echo "This merge completed without triggering a release"
|
package/DEVELOPING.md
CHANGED
|
@@ -54,7 +54,7 @@ npm run examples
|
|
|
54
54
|
### Cache Behavior
|
|
55
55
|
- **Cache Hit**: Returns cached response instantly
|
|
56
56
|
- **Cache Miss**: Makes LLM API call and caches the response
|
|
57
|
-
- **TTL**: Cached responses expire
|
|
57
|
+
- **TTL**: Cached responses expire after 365 days (configurable via `CHATGPT_CACHE_TTL`)
|
|
58
58
|
- **Fallback**: Gracefully handles Redis connection failures
|
|
59
59
|
|
|
60
60
|
## Development Workflow
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@far-world-labs/verblets",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Verblets is a collection of tools for building LLM-powered applications.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -15,20 +15,20 @@
|
|
|
15
15
|
"--": "npm run script -- generate-verblet foo",
|
|
16
16
|
"script": "./scripts/run.sh",
|
|
17
17
|
"test": "vitest",
|
|
18
|
+
"clear-test-cache": "node scripts/clear-redis.js",
|
|
18
19
|
"examples:warn": "source .env && LLM_EXPECT_MODE=info EXAMPLES=true vitest --config .vitest.config.examples.js",
|
|
19
|
-
"examples": "source .env && LLM_EXPECT_MODE=error EXAMPLES=true vitest --config .vitest.config.examples.js",
|
|
20
|
+
"examples": "source .env && LLM_EXPECT_MODE=error EXAMPLES=true ENABLE_LONG_EXAMPLES=false vitest --config .vitest.config.examples.js",
|
|
21
|
+
"examples:all": "source .env && LLM_EXPECT_MODE=error EXAMPLES=true ENABLE_LONG_EXAMPLES=true vitest --config .vitest.config.examples.js",
|
|
22
|
+
"examples:fresh": "npm run clear-test-cache && npm run examples:all",
|
|
20
23
|
"lint": "eslint 'src/**/*.{js,jsx}'",
|
|
21
24
|
"lint:fix": "eslint 'src/**/*.{js,jsx}' --fix",
|
|
22
25
|
"check:deps": "npx npm-deprecated-check current",
|
|
23
26
|
"husky:install": "husky install",
|
|
24
27
|
"husky:uninstall": "husky uninstall",
|
|
25
28
|
"prepare": "npx husky install",
|
|
26
|
-
"version:patch": "
|
|
27
|
-
"version:minor": "
|
|
28
|
-
"version:major": "
|
|
29
|
-
"release:patch": "node scripts/version-bump.js patch",
|
|
30
|
-
"release:minor": "node scripts/version-bump.js minor",
|
|
31
|
-
"release:major": "node scripts/version-bump.js major"
|
|
29
|
+
"version:patch": "npm version patch",
|
|
30
|
+
"version:minor": "npm version minor",
|
|
31
|
+
"version:major": "npm version major"
|
|
32
32
|
},
|
|
33
33
|
"config": {
|
|
34
34
|
"gen-script": "./scripts/run.sh gen-$1"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { getClient as getRedis } from '../src/services/redis/index.js';
|
|
4
|
+
|
|
5
|
+
async function clearRedisKeys() {
|
|
6
|
+
let redis = null;
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
console.log('🔄 Connecting to Redis...');
|
|
10
|
+
redis = await getRedis();
|
|
11
|
+
|
|
12
|
+
// Check if this is the NullRedisClient (in-memory fallback)
|
|
13
|
+
if (redis.store !== undefined) {
|
|
14
|
+
// This is the in-memory client
|
|
15
|
+
const keyCount = Object.keys(redis.store).length;
|
|
16
|
+
if (keyCount === 0) {
|
|
17
|
+
console.log('✅ In-memory cache is already empty - no keys to clear');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(`🗑️ Found ${keyCount} keys in in-memory cache to clear`);
|
|
22
|
+
redis.store = {};
|
|
23
|
+
console.log(`✅ Successfully cleared ${keyCount} in-memory cache keys`);
|
|
24
|
+
console.log('🧹 In-memory cache has been cleared - tests can now run with fresh responses');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// This is the SafeRedisClient wrapper - access underlying Redis client
|
|
29
|
+
const underlyingClient = redis.redisClient;
|
|
30
|
+
if (!underlyingClient) {
|
|
31
|
+
console.log('⚠️ No underlying Redis client found - using fallback method');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Get count of keys before clearing
|
|
36
|
+
const keys = await underlyingClient.keys('*');
|
|
37
|
+
const keyCount = keys.length;
|
|
38
|
+
|
|
39
|
+
if (keyCount === 0) {
|
|
40
|
+
console.log('✅ Redis is already empty - no keys to clear');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`🗑️ Found ${keyCount} keys to clear`);
|
|
45
|
+
|
|
46
|
+
// Use FLUSHDB to clear all keys in the current database
|
|
47
|
+
// This is more efficient and reliable than deleting individual keys
|
|
48
|
+
await underlyingClient.flushDb();
|
|
49
|
+
|
|
50
|
+
console.log(`✅ Successfully cleared all ${keyCount} Redis keys`);
|
|
51
|
+
console.log('🧹 Redis cache has been cleared - tests can now run with fresh responses');
|
|
52
|
+
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (error.message.includes('ECONNREFUSED') || error.message.includes('connection')) {
|
|
55
|
+
console.log('⚠️ Redis is not running or not accessible - nothing to clear');
|
|
56
|
+
console.log(' This is normal if you\'re using the in-memory cache fallback');
|
|
57
|
+
} else {
|
|
58
|
+
console.error('❌ Error clearing Redis keys:', error.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
if (redis && typeof redis.disconnect === 'function') {
|
|
63
|
+
try {
|
|
64
|
+
await redis.disconnect();
|
|
65
|
+
console.log('🔌 Disconnected from Redis');
|
|
66
|
+
} catch (disconnectError) {
|
|
67
|
+
// Ignore disconnect errors
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Run the script
|
|
74
|
+
clearRedisKeys();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Conversation
|
|
2
|
+
|
|
3
|
+
A flexible engine that produces multi-speaker transcripts on demand. Provide a list of speakers and conversation rules. The chain manages turn taking, facilitator remarks, and closing summaries.
|
|
4
|
+
|
|
5
|
+
```javascript
|
|
6
|
+
import Conversation from './src/chains/conversation/index.js';
|
|
7
|
+
|
|
8
|
+
const speakers = [
|
|
9
|
+
{ id: 'fac', role: 'facilitator', bio: 'organizes community events' },
|
|
10
|
+
{ id: 'max', bio: 'local baker' },
|
|
11
|
+
{ id: 'lily', bio: 'youth soccer coach' },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const chain = new Conversation('neighborhood picnic', speakers, {
|
|
15
|
+
rules: { shouldContinue: (round) => round < 2 },
|
|
16
|
+
});
|
|
17
|
+
const transcript = await chain.run();
|
|
18
|
+
console.log(transcript);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Each message in the transcript has the shape `{ id, name, comment, time }` where
|
|
22
|
+
`time` is in `HH:MM` format.
|
|
23
|
+
|
|
24
|
+
The conversation engine uses `conversation-turn-multi` and `conversation-turn` verblets internally to generate contextual responses. You can supply custom `bulkSpeakFn` and `speakFn` implementations for specialized conversation behaviors.
|
|
25
|
+
|
|
26
|
+
Perfect for simulating realistic discussions, focus groups, team meetings, or any multi-participant dialogue where each speaker brings their unique perspective and expertise to the conversation.
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import ConversationChain from './index.js';
|
|
3
|
+
import { expect as aiExpected } from '../expect/index.js';
|
|
4
|
+
import { longTestTimeout, shouldRunLongExamples } from '../../constants/common.js';
|
|
5
|
+
import { roundRobin } from './turn-policies.js';
|
|
6
|
+
|
|
7
|
+
describe('conversation chain examples', () => {
|
|
8
|
+
it.skipIf(!shouldRunLongExamples)(
|
|
9
|
+
'generates a debate on consciousness emergence in AI systems - a current open question',
|
|
10
|
+
async () => {
|
|
11
|
+
const speakers = [
|
|
12
|
+
{
|
|
13
|
+
id: 'turing',
|
|
14
|
+
name: 'Alan Turing',
|
|
15
|
+
bio: 'Father of computer science and artificial intelligence, creator of the Turing Test',
|
|
16
|
+
agenda:
|
|
17
|
+
'Argue that consciousness is fundamentally about behavior and computational processes, not substrate',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'minsky',
|
|
21
|
+
name: 'Marvin Minsky',
|
|
22
|
+
bio: 'Co-founder of MIT AI Lab, pioneer in artificial intelligence and cognitive science',
|
|
23
|
+
agenda:
|
|
24
|
+
'Advocate that consciousness emerges from the interaction of simple, unconscious agents in a society of mind',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'hinton',
|
|
28
|
+
name: 'Geoffrey Hinton',
|
|
29
|
+
bio: 'Godfather of deep learning, pioneer in neural networks and backpropagation',
|
|
30
|
+
agenda:
|
|
31
|
+
'Argue that consciousness could emerge from sufficiently complex neural networks through self-organizing principles',
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const topic =
|
|
36
|
+
'The Hard Problem of Machine Consciousness: Will AI systems develop genuine subjective experience?';
|
|
37
|
+
|
|
38
|
+
// Hook: Pre-conversation setup
|
|
39
|
+
expect(speakers.length).toBe(3);
|
|
40
|
+
expect(topic.toLowerCase()).toContain('consciousness');
|
|
41
|
+
// BREAKPOINT: Set breakpoint here to inspect speakers and topic
|
|
42
|
+
|
|
43
|
+
const shouldContinueWithHook = (round, _messages) => {
|
|
44
|
+
// Hook: Simple round tracking
|
|
45
|
+
// BREAKPOINT: Set breakpoint here to see round progression and messages
|
|
46
|
+
expect(round).toBeGreaterThanOrEqual(0);
|
|
47
|
+
return round < 3; // 3 rounds to ensure all speakers participate
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const chain = new ConversationChain(topic, speakers, {
|
|
51
|
+
rules: {
|
|
52
|
+
shouldContinue: shouldContinueWithHook,
|
|
53
|
+
turnPolicy: roundRobin(speakers), // Use deterministic round-robin to ensure all speakers participate
|
|
54
|
+
customPrompt:
|
|
55
|
+
"This is a deep philosophical and scientific discussion. Draw on your expertise and engage with others' arguments. Be intellectually rigorous but concise.",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Hook: Pre-run validation
|
|
60
|
+
expect(chain.speakers.length).toBe(3);
|
|
61
|
+
// BREAKPOINT: Set breakpoint here before conversation starts
|
|
62
|
+
|
|
63
|
+
const messages = await chain.run();
|
|
64
|
+
|
|
65
|
+
// Hook: Post-run analysis
|
|
66
|
+
// BREAKPOINT: Set breakpoint here to examine completed conversation
|
|
67
|
+
expect(messages.length).toBeGreaterThan(2); // At least 3 messages (one per speaker)
|
|
68
|
+
|
|
69
|
+
// Hook: Final participation check
|
|
70
|
+
const speakerIds = new Set(messages.map((m) => m.id));
|
|
71
|
+
expect(speakerIds.size).toBe(3);
|
|
72
|
+
// BREAKPOINT: Set breakpoint here for final analysis
|
|
73
|
+
|
|
74
|
+
// Basic validation
|
|
75
|
+
expect(Array.isArray(messages)).toBe(true);
|
|
76
|
+
|
|
77
|
+
// Hook: Speaker participation analysis
|
|
78
|
+
expect(speakerIds.has('turing')).toBe(true);
|
|
79
|
+
expect(speakerIds.has('minsky')).toBe(true);
|
|
80
|
+
expect(speakerIds.has('hinton')).toBe(true);
|
|
81
|
+
|
|
82
|
+
// Messages should have proper structure
|
|
83
|
+
for (const message of messages) {
|
|
84
|
+
expect(message).toHaveProperty('id');
|
|
85
|
+
expect(message).toHaveProperty('name');
|
|
86
|
+
expect(message).toHaveProperty('comment');
|
|
87
|
+
expect(message).toHaveProperty('time');
|
|
88
|
+
expect(typeof message.comment).toBe('string');
|
|
89
|
+
expect(message.comment.length).toBeGreaterThan(0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Hook: Content analysis
|
|
93
|
+
const allComments = messages
|
|
94
|
+
.map((m) => m.comment)
|
|
95
|
+
.join(' ')
|
|
96
|
+
.toLowerCase();
|
|
97
|
+
const consciousnessTerms = ['consciousness', 'subjective', 'experience', 'awareness'];
|
|
98
|
+
const foundTerms = consciousnessTerms.filter((term) => allComments.includes(term));
|
|
99
|
+
|
|
100
|
+
expect(foundTerms.length).toBeGreaterThan(0);
|
|
101
|
+
|
|
102
|
+
// AI validation of conversation quality
|
|
103
|
+
const [hasPhilosophicalDepth] = await aiExpected(
|
|
104
|
+
messages,
|
|
105
|
+
undefined,
|
|
106
|
+
'Should contain sophisticated philosophical discussion about machine consciousness with each pioneer contributing their unique perspective'
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Hook: Final validation
|
|
110
|
+
expect(hasPhilosophicalDepth).toBe(true);
|
|
111
|
+
},
|
|
112
|
+
longTestTimeout
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
it.skipIf(!shouldRunLongExamples)(
|
|
116
|
+
'generates a debate between modern AI researchers with debugging hooks',
|
|
117
|
+
async () => {
|
|
118
|
+
// Hook: Test initialization
|
|
119
|
+
|
|
120
|
+
const speakers = [
|
|
121
|
+
{
|
|
122
|
+
id: 'moderator',
|
|
123
|
+
name: 'AI Debate Moderator',
|
|
124
|
+
bio: 'Expert moderator facilitating discussions on AI research directions. Ask probing questions and guide the conversation.',
|
|
125
|
+
agenda: 'Keep the discussion focused and ensure all perspectives are heard',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'hinton',
|
|
129
|
+
name: 'Geoffrey Hinton',
|
|
130
|
+
bio: 'Godfather of deep learning, pioneer in neural networks and backpropagation',
|
|
131
|
+
agenda: 'Advocate for deep learning and neural network approaches to AI',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'li',
|
|
135
|
+
name: 'Fei-Fei Li',
|
|
136
|
+
bio: 'Pioneer in computer vision and AI ethics, former Chief Scientist at Google Cloud',
|
|
137
|
+
agenda: 'Emphasize the importance of visual intelligence and responsible AI development',
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const topic =
|
|
142
|
+
'Deep Learning vs Symbolic AI: Which approach will lead to artificial general intelligence?';
|
|
143
|
+
|
|
144
|
+
// Custom turn policy with hooks
|
|
145
|
+
const moderatedTurnPolicy = (round, _messages) => {
|
|
146
|
+
// Hook: Simple turn policy tracking
|
|
147
|
+
expect(round).toBeGreaterThanOrEqual(0);
|
|
148
|
+
|
|
149
|
+
if (round === 0) {
|
|
150
|
+
return ['moderator', 'hinton', 'li'];
|
|
151
|
+
} else {
|
|
152
|
+
return ['hinton', 'li', 'moderator'];
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const chain = new ConversationChain(topic, speakers, {
|
|
157
|
+
rules: {
|
|
158
|
+
shouldContinue: (round, _messages) => {
|
|
159
|
+
// Hook: Simple continuation check
|
|
160
|
+
return round < 2; // Only 2 rounds
|
|
161
|
+
},
|
|
162
|
+
turnPolicy: moderatedTurnPolicy,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Hook: Pre-execution state
|
|
167
|
+
expect(chain.speakers.length).toBe(3);
|
|
168
|
+
|
|
169
|
+
const messages = await chain.run();
|
|
170
|
+
|
|
171
|
+
// Hook: Simple results
|
|
172
|
+
expect(Array.isArray(messages)).toBe(true);
|
|
173
|
+
expect(messages.length).toBeGreaterThan(4);
|
|
174
|
+
|
|
175
|
+
// Hook: Moderator participation check
|
|
176
|
+
const moderatorMessages = messages.filter((m) => m.id === 'moderator');
|
|
177
|
+
expect(moderatorMessages.length).toBeGreaterThan(0);
|
|
178
|
+
|
|
179
|
+
// Hook: Researcher participation check
|
|
180
|
+
const researcherIds = ['hinton', 'li'];
|
|
181
|
+
researcherIds.forEach((id) => {
|
|
182
|
+
const count = messages.filter((m) => m.id === id).length;
|
|
183
|
+
expect(count).toBeGreaterThan(0);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// AI validation of moderated discussion
|
|
187
|
+
const [hasModeratedDiscussion] = await aiExpected(
|
|
188
|
+
messages,
|
|
189
|
+
undefined,
|
|
190
|
+
'Should contain a well-moderated discussion with the moderator guiding the conversation and researchers providing technical insights'
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Hook: Final assessment
|
|
194
|
+
expect(hasModeratedDiscussion).toBe(true);
|
|
195
|
+
},
|
|
196
|
+
longTestTimeout
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
it.skipIf(!shouldRunLongExamples)(
|
|
200
|
+
'generates a historical debate between early AI pioneers with custom turn policy',
|
|
201
|
+
async () => {
|
|
202
|
+
const speakers = [
|
|
203
|
+
{
|
|
204
|
+
id: 'turing',
|
|
205
|
+
name: 'Alan Turing',
|
|
206
|
+
bio: 'Father of computer science, proposed the Turing Test in 1950',
|
|
207
|
+
agenda: 'Argue that machines can think and exhibit intelligent behavior',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
id: 'minsky',
|
|
211
|
+
name: 'Marvin Minsky',
|
|
212
|
+
bio: 'Co-founder of MIT AI Lab, expert in cognitive science and AI',
|
|
213
|
+
agenda: 'Discuss the society of mind and modular intelligence',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'mccarthy',
|
|
217
|
+
name: 'John McCarthy',
|
|
218
|
+
bio: 'Inventor of LISP, advocate for logical AI approaches',
|
|
219
|
+
agenda: 'Promote formal logic and symbolic reasoning in AI',
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
const topic = 'Can machines truly think, or do they merely simulate thinking?';
|
|
224
|
+
|
|
225
|
+
// Custom turn policy: alternate between Turing and others
|
|
226
|
+
const customTurnPolicy = (round) => {
|
|
227
|
+
if (round === 0) {
|
|
228
|
+
return ['turing', 'minsky', 'mccarthy'];
|
|
229
|
+
} else {
|
|
230
|
+
return ['minsky', 'mccarthy', 'turing'];
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const chain = new ConversationChain(topic, speakers, {
|
|
235
|
+
rules: {
|
|
236
|
+
shouldContinue: (round) => round < 2, // Only 2 rounds
|
|
237
|
+
turnPolicy: customTurnPolicy,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const messages = await chain.run();
|
|
242
|
+
|
|
243
|
+
// Validate turn policy was followed
|
|
244
|
+
expect(messages.length).toBeGreaterThan(4);
|
|
245
|
+
|
|
246
|
+
// All speakers should contribute
|
|
247
|
+
expect(messages.some((m) => m.id === 'turing')).toBe(true);
|
|
248
|
+
expect(messages.some((m) => m.id === 'minsky')).toBe(true);
|
|
249
|
+
expect(messages.some((m) => m.id === 'mccarthy')).toBe(true);
|
|
250
|
+
|
|
251
|
+
// AI validation of philosophical depth
|
|
252
|
+
const [hasPhilosophicalDepth] = await aiExpected(
|
|
253
|
+
messages,
|
|
254
|
+
undefined,
|
|
255
|
+
'Should contain deep philosophical discussion about machine consciousness and the nature of thinking'
|
|
256
|
+
);
|
|
257
|
+
expect(hasPhilosophicalDepth).toBe(true);
|
|
258
|
+
},
|
|
259
|
+
longTestTimeout
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
it(
|
|
263
|
+
'handles conversation with custom prompts and includes a summarizer role',
|
|
264
|
+
async () => {
|
|
265
|
+
const speakers = [
|
|
266
|
+
{
|
|
267
|
+
id: 'hinton',
|
|
268
|
+
name: 'Geoffrey Hinton',
|
|
269
|
+
bio: 'Deep learning pioneer, recently left Google to warn about AI risks',
|
|
270
|
+
agenda: 'Discuss both the potential and dangers of advanced AI systems',
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: 'sutskever',
|
|
274
|
+
name: 'Ilya Sutskever',
|
|
275
|
+
bio: 'OpenAI co-founder, architect of GPT models',
|
|
276
|
+
agenda: 'Focus on the technical path to AGI and alignment challenges',
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: 'summarizer',
|
|
280
|
+
name: 'Discussion Summarizer',
|
|
281
|
+
bio: 'Expert at synthesizing complex technical discussions. Provide clear summaries of key points.',
|
|
282
|
+
agenda:
|
|
283
|
+
'Summarize the main arguments and highlight important insights from the discussion',
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
const topic = 'AI Safety and the Race to AGI: Balancing Progress with Precaution';
|
|
288
|
+
|
|
289
|
+
const customPrompt =
|
|
290
|
+
'You are participating in a high-stakes debate about AI safety. Be thoughtful, cite specific examples, and acknowledge the complexity of the issues.';
|
|
291
|
+
|
|
292
|
+
// Turn policy: discussion round, then summarizer
|
|
293
|
+
const summaryTurnPolicy = (round) => {
|
|
294
|
+
if (round === 0) {
|
|
295
|
+
return ['hinton', 'sutskever'];
|
|
296
|
+
} else if (round === 1) {
|
|
297
|
+
return ['summarizer'];
|
|
298
|
+
} else {
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const chain = new ConversationChain(topic, speakers, {
|
|
304
|
+
rules: {
|
|
305
|
+
shouldContinue: (round) => round < 2, // Only 2 rounds
|
|
306
|
+
turnPolicy: summaryTurnPolicy,
|
|
307
|
+
customPrompt,
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const messages = await chain.run();
|
|
312
|
+
|
|
313
|
+
// Validate structure
|
|
314
|
+
expect(messages.length).toBeGreaterThan(2);
|
|
315
|
+
expect(messages.some((m) => m.id === 'hinton')).toBe(true);
|
|
316
|
+
expect(messages.some((m) => m.id === 'sutskever')).toBe(true);
|
|
317
|
+
expect(messages.some((m) => m.id === 'summarizer')).toBe(true);
|
|
318
|
+
|
|
319
|
+
// Summarizer should come last
|
|
320
|
+
const lastMessage = messages[messages.length - 1];
|
|
321
|
+
expect(lastMessage.id).toBe('summarizer');
|
|
322
|
+
|
|
323
|
+
// AI validation for safety-focused discussion with summary
|
|
324
|
+
const [focusesOnSafety] = await aiExpected(
|
|
325
|
+
messages,
|
|
326
|
+
undefined,
|
|
327
|
+
'Should contain substantive discussion about AI safety, risks, and responsible AGI development, concluding with a clear summary'
|
|
328
|
+
);
|
|
329
|
+
expect(focusesOnSafety).toBe(true);
|
|
330
|
+
},
|
|
331
|
+
longTestTimeout
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
it(
|
|
335
|
+
'demonstrates flexible speaker ordering and role definitions',
|
|
336
|
+
async () => {
|
|
337
|
+
const speakers = [
|
|
338
|
+
{
|
|
339
|
+
id: 'questioner',
|
|
340
|
+
name: 'Socratic Questioner',
|
|
341
|
+
bio: 'Ask probing questions about AI consciousness and challenge assumptions',
|
|
342
|
+
agenda: 'Use the Socratic method to explore deeper truths about machine intelligence',
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: 'turing',
|
|
346
|
+
name: 'Alan Turing',
|
|
347
|
+
bio: 'Father of computer science, creator of the Turing Test',
|
|
348
|
+
agenda: 'Defend the possibility of machine consciousness and thinking',
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: 'skeptic',
|
|
352
|
+
name: 'AI Skeptic',
|
|
353
|
+
bio: 'Philosopher who questions whether machines can truly understand or just manipulate symbols',
|
|
354
|
+
agenda: 'Challenge claims about machine consciousness and understanding',
|
|
355
|
+
},
|
|
356
|
+
];
|
|
357
|
+
|
|
358
|
+
const topic = 'What does it mean for a machine to truly understand?';
|
|
359
|
+
|
|
360
|
+
// Dynamic turn policy based on conversation flow
|
|
361
|
+
const dynamicTurnPolicy = (round, _history) => {
|
|
362
|
+
if (round === 0) {
|
|
363
|
+
return ['questioner', 'turing', 'skeptic'];
|
|
364
|
+
} else {
|
|
365
|
+
// Final round: questioner gets last word
|
|
366
|
+
return ['turing', 'skeptic', 'questioner'];
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const chain = new ConversationChain(topic, speakers, {
|
|
371
|
+
rules: {
|
|
372
|
+
shouldContinue: (round) => round < 2, // Only 2 rounds
|
|
373
|
+
turnPolicy: dynamicTurnPolicy,
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const messages = await chain.run();
|
|
378
|
+
|
|
379
|
+
// Validate all speakers participated
|
|
380
|
+
expect(messages.some((m) => m.id === 'questioner')).toBe(true);
|
|
381
|
+
expect(messages.some((m) => m.id === 'turing')).toBe(true);
|
|
382
|
+
expect(messages.some((m) => m.id === 'skeptic')).toBe(true);
|
|
383
|
+
|
|
384
|
+
// Questioner should have the final word
|
|
385
|
+
const lastMessage = messages[messages.length - 1];
|
|
386
|
+
expect(lastMessage.id).toBe('questioner');
|
|
387
|
+
|
|
388
|
+
// AI validation of Socratic dialogue
|
|
389
|
+
const [hasSocraticDepth] = await aiExpected(
|
|
390
|
+
messages,
|
|
391
|
+
undefined,
|
|
392
|
+
'Should demonstrate Socratic questioning method with deep philosophical inquiry about machine understanding'
|
|
393
|
+
);
|
|
394
|
+
expect(hasSocraticDepth).toBe(true);
|
|
395
|
+
},
|
|
396
|
+
longTestTimeout
|
|
397
|
+
);
|
|
398
|
+
});
|