Skip to content

Commit e1d726d

Browse files
authored
refactor: add agent-specific routes (#23)
1 parent 0e5b4db commit e1d726d

File tree

20 files changed

+1343
-276
lines changed

20 files changed

+1343
-276
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ permissions:
1212

1313
jobs:
1414
build:
15-
name: Build
15+
name: Build and Test
1616
runs-on: ubuntu-latest
1717

1818
steps:
@@ -51,3 +51,9 @@ jobs:
5151

5252
- name: Build
5353
run: turbo build
54+
55+
- name: Setup test environment
56+
run: cp packages/agents/sample.config.toml packages/agents/config.toml
57+
58+
- name: Test
59+
run: turbo test

packages/agents/__tests__/answerGenerator.test.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,24 @@ import { IterableReadableStream } from '@langchain/core/utils/stream';
1313
import { BaseMessage, BaseMessageChunk } from '@langchain/core/messages';
1414
import { BaseLanguageModelInput } from '@langchain/core/language_models/base';
1515

16-
1716
// Mock the formatChatHistoryAsString utility
1817
jest.mock('../src/utils/index', () => ({
1918
__esModule: true,
20-
formatChatHistoryAsString: jest.fn().mockImplementation(() => 'mocked chat history'),
19+
formatChatHistoryAsString: jest
20+
.fn()
21+
.mockImplementation(() => 'mocked chat history'),
2122
logger: {
2223
info: jest.fn(),
2324
debug: jest.fn(),
2425
error: jest.fn(),
25-
}
26+
},
27+
TokenTracker: {
28+
trackFullUsage: jest.fn().mockReturnValue({
29+
promptTokens: 100,
30+
responseTokens: 50,
31+
totalTokens: 150,
32+
}),
33+
},
2634
}));
2735

2836
// No need to separately mock the logger since it's now mocked as part of utils/index

packages/agents/__tests__/queryProcessor.test.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import { QueryProcessor } from '../src/core/pipeline/queryProcessor';
22
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
3-
import {
4-
RagInput,
5-
RagSearchConfig,
6-
DocumentSource,
7-
} from '../src/types/index';
3+
import { RagInput, RagSearchConfig, DocumentSource } from '../src/types/index';
84
import { mockDeep, MockProxy } from 'jest-mock-extended';
95
import { AIMessage } from '@langchain/core/messages';
106

@@ -15,14 +11,23 @@ jest.mock('../src/utils/index', () => ({
1511
debug: jest.fn(),
1612
error: jest.fn(),
1713
},
18-
formatChatHistoryAsString: jest.fn((history) =>
19-
history.map((message) => `${message._getType()}: ${message.content}`).join('\n')
14+
formatChatHistoryAsString: jest.fn((history) =>
15+
history
16+
.map((message) => `${message._getType()}: ${message.content}`)
17+
.join('\n'),
2018
),
2119
parseXMLContent: jest.fn((xml, tag) => {
2220
const regex = new RegExp(`<${tag}>(.*?)</${tag}>`, 'gs');
2321
const matches = [...xml.matchAll(regex)];
2422
return matches.map((match) => match[1].trim());
2523
}),
24+
TokenTracker: {
25+
trackFullUsage: jest.fn().mockReturnValue({
26+
promptTokens: 100,
27+
completionTokens: 50,
28+
totalTokens: 150,
29+
}),
30+
},
2631
}));
2732

2833
describe('QueryProcessor', () => {

packages/agents/__tests__/ragAgentFactory.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { RagPipeline } from '../src/core/pipeline/ragPipeline';
33
import { AvailableAgents, LLMConfig, DocumentSource } from '../src/types';
44
import { Embeddings } from '@langchain/core/embeddings';
55
import { BaseChatModel } from '@langchain/core/language_models/chat_models';
6-
import { VectorStore } from '../src/db/vectorStore';
76
import { mockDeep, MockProxy } from 'jest-mock-extended';
87
import { BaseMessage } from '@langchain/core/messages';
98
import EventEmitter from 'events';
9+
import { VectorStore } from '../src/db/postgresVectorStore';
1010

1111
// Mock the agent configuration and RagPipeline
1212
jest.mock('../src/config/agent', () => ({
@@ -103,7 +103,7 @@ describe('RagAgentFactory', () => {
103103
// Assert
104104
expect(RagPipeline).toHaveBeenCalledTimes(1);
105105
expect(emitter).toBeInstanceOf(EventEmitter);
106-
106+
107107
// Check streaming option is passed
108108
const executeSpy = (RagPipeline as jest.Mock).mock.results[0].value
109109
.execute;

packages/agents/__tests__/ragPipeline.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,20 @@ jest.mock('../src/utils/index', () => ({
2828
info: jest.fn(),
2929
debug: jest.fn(),
3030
error: jest.fn(),
31-
}
31+
},
32+
TokenTracker: {
33+
resetSessionCounters: jest.fn(),
34+
getSessionTokenUsage: jest.fn().mockReturnValue({
35+
promptTokens: 100,
36+
responseTokens: 50,
37+
totalTokens: 150,
38+
}),
39+
trackFullUsage: jest.fn().mockReturnValue({
40+
promptTokens: 100,
41+
completionTokens: 50,
42+
totalTokens: 150,
43+
}),
44+
},
3245
}));
3346

3447
describe('RagPipeline', () => {

packages/agents/sample.config.toml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
[API_KEYS]
2-
OPENAI = ""
3-
GROQ = ""
4-
ANTHROPIC = ""
5-
DEEPSEEK = ""
6-
GEMINI = ""
2+
OPENAI = "<your_openai_api_key>"
3+
GROQ = "<your_groq_api_key>"
4+
ANTHROPIC = "<your_anthropic_api_key>"
5+
DEEPSEEK = "<your_deepseek_api_key>"
6+
GEMINI = "<your_gemini_api_key>"
77

88
[VECTOR_DB]
9-
POSTGRES_USER="cairocoder"
10-
POSTGRES_PASSWORD="cairocoder"
11-
POSTGRES_DB="cairocoder"
12-
POSTGRES_HOST="postgres"
13-
POSTGRES_PORT="5432"
9+
POSTGRES_USER = "cairocoder"
10+
POSTGRES_PASSWORD = "cairocoder"
11+
POSTGRES_DB = "cairocoder"
12+
POSTGRES_HOST = "postgres"
13+
POSTGRES_PORT = "5432"
1414

1515
[GENERAL]
1616
PORT = 3_001
@@ -22,4 +22,4 @@ DEFAULT_CHAT_MODEL = "Gemini Flash 2.5"
2222
DEFAULT_FAST_CHAT_PROVIDER = "gemini"
2323
DEFAULT_FAST_CHAT_MODEL = "Gemini Flash 2.5"
2424
DEFAULT_EMBEDDING_PROVIDER = "openai"
25-
DEFAULT_EMBEDDING_MODEL = "Text embedding 3 large"
25+
DEFAULT_EMBEDDING_MODEL = "Text embedding 3 large"
Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,41 @@
1-
import { basicContractTemplate } from './templates/contractTemplate';
2-
import { cairoCoderPrompts } from './prompts';
3-
import { basicTestTemplate } from './templates/testTemplate';
41
import { VectorStore } from '../db/postgresVectorStore';
5-
import { DocumentSource, RagSearchConfig } from '../types';
2+
import { RagSearchConfig } from '../types';
3+
import { getAgent, getDefaultAgent } from './agents';
64

5+
// Legacy function for backward compatibility
76
export const getAgentConfig = (vectorStore: VectorStore): RagSearchConfig => {
7+
const agent = getDefaultAgent();
88
return {
9-
name: 'Cairo Coder',
10-
prompts: cairoCoderPrompts,
9+
name: agent.name,
10+
prompts: agent.prompts,
1111
vectorStore,
12-
contractTemplate: basicContractTemplate,
13-
testTemplate: basicTestTemplate,
14-
maxSourceCount: 15,
15-
similarityThreshold: 0.4,
16-
sources: [
17-
DocumentSource.CAIRO_BOOK,
18-
DocumentSource.CAIRO_BY_EXAMPLE,
19-
DocumentSource.STARKNET_FOUNDRY,
20-
DocumentSource.CORELIB_DOCS,
21-
DocumentSource.OPENZEPPELIN_DOCS,
22-
DocumentSource.SCARB_DOCS,
23-
],
12+
contractTemplate: agent.templates?.contract,
13+
testTemplate: agent.templates?.test,
14+
maxSourceCount: agent.parameters.maxSourceCount,
15+
similarityThreshold: agent.parameters.similarityThreshold,
16+
sources: agent.sources,
17+
};
18+
};
19+
20+
// Get agent configuration by ID
21+
export const getAgentConfigById = async (
22+
agentId: string,
23+
vectorStore: VectorStore,
24+
): Promise<RagSearchConfig> => {
25+
const agentConfig = getAgent(agentId);
26+
27+
if (!agentConfig) {
28+
throw new Error(`Agent configuration not found: ${agentId}`);
29+
}
30+
31+
return {
32+
name: agentConfig.name,
33+
prompts: agentConfig.prompts,
34+
vectorStore,
35+
contractTemplate: agentConfig.templates?.contract,
36+
testTemplate: agentConfig.templates?.test,
37+
maxSourceCount: agentConfig.parameters.maxSourceCount,
38+
similarityThreshold: agentConfig.parameters.similarityThreshold,
39+
sources: agentConfig.sources,
2440
};
2541
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { DocumentSource } from '../types';
2+
import { cairoCoderPrompts, scarbPrompts } from './prompts';
3+
import { basicContractTemplate } from './templates/contractTemplate';
4+
import { basicTestTemplate } from './templates/testTemplate';
5+
6+
export interface AgentConfiguration {
7+
id: string;
8+
name: string;
9+
description: string;
10+
sources: DocumentSource[];
11+
prompts: {
12+
searchRetrieverPrompt: string;
13+
searchResponsePrompt: string;
14+
noSourceFoundPrompt?: string;
15+
};
16+
templates?: {
17+
contract?: string;
18+
test?: string;
19+
};
20+
parameters: {
21+
maxSourceCount: number;
22+
similarityThreshold: number;
23+
};
24+
}
25+
26+
// All pre-configured agents
27+
export const AGENTS: Record<string, AgentConfiguration> = {
28+
'cairo-coder': {
29+
id: 'cairo-coder',
30+
name: 'Cairo Coder',
31+
description:
32+
'Default Cairo language and smart contract assistant with full documentation access',
33+
sources: [
34+
DocumentSource.CAIRO_BOOK,
35+
DocumentSource.CAIRO_BY_EXAMPLE,
36+
DocumentSource.STARKNET_FOUNDRY,
37+
DocumentSource.CORELIB_DOCS,
38+
DocumentSource.OPENZEPPELIN_DOCS,
39+
DocumentSource.SCARB_DOCS,
40+
],
41+
prompts: cairoCoderPrompts,
42+
templates: {
43+
contract: basicContractTemplate,
44+
test: basicTestTemplate,
45+
},
46+
parameters: {
47+
maxSourceCount: 15,
48+
similarityThreshold: 0.4,
49+
},
50+
},
51+
'scarb-assistant': {
52+
id: 'scarb-assistant',
53+
name: 'Scarb Assistant',
54+
description: 'Specialized Scarb build tool assistance',
55+
sources: [DocumentSource.SCARB_DOCS],
56+
prompts: scarbPrompts,
57+
parameters: {
58+
maxSourceCount: 10,
59+
similarityThreshold: 0.5,
60+
},
61+
},
62+
};
63+
64+
// Helper functions
65+
export function getAgent(agentId: string): AgentConfiguration | undefined {
66+
return AGENTS[agentId];
67+
}
68+
69+
export function listAgents(): AgentConfiguration[] {
70+
return Object.values(AGENTS);
71+
}
72+
73+
export function getDefaultAgent(): AgentConfiguration {
74+
return AGENTS['cairo-coder'];
75+
}
Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,33 @@
1-
21
import {
32
CAIROCODER_NO_SOURCE_PROMPT,
43
CAIROCODER_RESPONSE_PROMPT,
54
CAIROCODER_RETRIEVER_PROMPT,
65
} from './cairoCoderPrompts';
76

8-
import { AgentPrompts } from '../../types';
7+
import {
8+
SCARB_NO_SOURCE_PROMPT,
9+
SCARB_RESPONSE_PROMPT,
10+
SCARB_RETRIEVER_PROMPT,
11+
} from './scarbPrompts';
912

13+
import { AgentPrompts } from '../../types';
1014

1115
export const cairoCoderPrompts: AgentPrompts = {
1216
searchRetrieverPrompt: CAIROCODER_RETRIEVER_PROMPT,
1317
searchResponsePrompt: CAIROCODER_RESPONSE_PROMPT,
1418
noSourceFoundPrompt: CAIROCODER_NO_SOURCE_PROMPT,
1519
};
1620

21+
export const scarbPrompts: AgentPrompts = {
22+
searchRetrieverPrompt: SCARB_RETRIEVER_PROMPT,
23+
searchResponsePrompt: SCARB_RESPONSE_PROMPT,
24+
noSourceFoundPrompt: SCARB_NO_SOURCE_PROMPT,
25+
};
26+
1727
// Helper function to inject dynamic values into prompts
1828
export const injectPromptVariables = (prompt: string): string => {
19-
return prompt.replace('${new Date().toISOString()}', new Date().toISOString());
29+
return prompt.replace(
30+
'${new Date().toISOString()}',
31+
new Date().toISOString(),
32+
);
2033
};

0 commit comments

Comments
 (0)