Skip to content

Commit 6848b70

Browse files
0.4.0 (#40)
* UI and logging updates * Add agent force stop route * Add Groq Llama 3.3 70b * Improve repo summary index generation * Add Jira.createIssue * Default the setTracer checkForcedStoppedFunc for tests * Add Google Cloud security command center function * Add Gemini Flash 2.0 support
1 parent a33d39d commit 6848b70

31 files changed

+907
-117
lines changed

CONVENTIONS.md

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,3 @@ Use async/await where possible
77
Test exceptional cases first and return/throw early.
88

99
Never edit files name CONVENTIONS.md or .cursorrules
10-
11-
# Test code standards
12-
13-
Unit test files should be in the same directory as the source file.
14-
15-
Any usage of chai-as-promised should use async/await
16-
```
17-
it('should work well with async/await', async () => {
18-
(await Promise.resolve(42)).should.equal(42)
19-
await Promise.reject(new Error()).should.be.rejectedWith(Error);
20-
});
21-
```
22-
23-
# Tool/function classes
24-
25-
Function classes with the @funcClass(__filename) must only have the default constructor.
26-
27-
Always use the Filesystem class in src/functions/storage/filesystem.ts to read/search/write to the local filesystem.

frontend/src/app/modules/agents/agent/agent-details/agent-details.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@if(agentDetails) {
22
<mat-card *ngIf="agentDetails.state === 'feedback'" class="p-3 mb-4">
33
<form [formGroup]="feedbackForm" (ngSubmit)="onSubmitFeedback()">
4-
<mat-card-title>Feedback Requested</mat-card-title>
4+
<mat-card-title class="font-bold pl-5 text-lg">Feedback Requested</mat-card-title>
55
<mat-card-content style="margin-bottom: 0; margin-top: 15px">
66
<mat-expansion-panel
77
*ngIf="agentDetails.functionCallHistory && agentDetails.functionCallHistory.length > 0"
@@ -42,7 +42,7 @@
4242

4343
<mat-card *ngIf="agentDetails?.state === 'error'" class="p-3 mb-4">
4444
<form [formGroup]="errorForm" (ngSubmit)="onResumeError()">
45-
<mat-card-title>Agent Error</mat-card-title>
45+
<mat-card-title class="font-bold pl-5 text-lg">Agent Error</mat-card-title>
4646
<mat-card-content style="margin-bottom: 0; margin-top: 15px">
4747
<mat-expansion-panel *ngIf="agentDetails.error" style="width: 1000px; margin-bottom: 20px">
4848
<mat-expansion-panel-header>
@@ -75,7 +75,7 @@
7575

7676
<mat-card *ngIf="agentDetails?.state === 'hil'" class="p-3 mb-4">
7777
<form [formGroup]="hilForm" (ngSubmit)="onResumeHil()">
78-
<mat-card-title>Human In Loop check</mat-card-title>
78+
<mat-card-title class="font-bold pl-5 text-lg">Human In Loop check</mat-card-title>
7979
<mat-card-content>
8080
<mat-form-field appearance="fill" class="full-width">
8181
<textarea

frontend/src/app/modules/agents/agent/agent-details/agent-details.component.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ export class AgentDetailsComponent implements OnInit {
123123
})
124124
).subscribe((response) => {
125125
if (response) {
126+
this.feedbackForm.reset();
126127
this.snackBar.open('Feedback submitted successfully', 'Close', { duration: 3000 });
127128
this.refreshAgentDetails();
128129
}
@@ -289,9 +290,7 @@ export class AgentDetailsComponent implements OnInit {
289290
});
290291

291292
dialogRef.afterClosed().subscribe((result) => {
292-
if (result) {
293-
this.resumeCompletedAgent(result.resumeInstructions);
294-
}
293+
if (result) this.resumeCompletedAgent(result.resumeInstructions);
295294
});
296295
}
297296

frontend/src/app/modules/agents/agent/agent-function-calls/agent-function-calls.component.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,20 @@ import { MatExpansionModule } from '@angular/material/expansion';
1212
<div class="mb-3 font-medium text-xl">{{ invoked.function_name }}</div>
1313
1414
<div *ngFor="let param of invoked.parameters | keyvalue">
15-
<p><strong>{{ param.key }}:</strong> {{ param.value }}</p>
15+
<div>
16+
<strong>{{ param.key }}:</strong>
17+
<ng-container *ngIf="param.value?.toString().length <= 200">
18+
{{ param.value }}
19+
</ng-container>
20+
<mat-expansion-panel *ngIf="param.value?.toString().length > 200" class="mt-4" #expansionPanel>
21+
<mat-expansion-panel-header [class.expanded-header]="expansionPanel.expanded">
22+
<mat-panel-title class="font-normal" *ngIf="!expansionPanel.expanded">
23+
{{ param.value?.toString().substring(0, 200) }}...
24+
</mat-panel-title>
25+
</mat-expansion-panel-header>
26+
<p>{{ param.value }}</p>
27+
</mat-expansion-panel>
28+
</div>
1629
</div>
1730
<mat-expansion-panel *ngIf="invoked.stdout" class="mt-4">
1831
<mat-expansion-panel-header>
@@ -29,6 +42,7 @@ import { MatExpansionModule } from '@angular/material/expansion';
2942
</div>
3043
</mat-card>
3144
`,
45+
styles: `.mat-expansion-panel-header.mat-expanded.expanded-header { height: 1.3em; padding-top: 1.2em; }`,
3246
standalone: true,
3347
imports: [CommonModule, MatCardModule, MatExpansionModule],
3448
})

frontend/src/app/modules/agents/new-agent/new-agent.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ export class NewAgentComponent implements OnInit {
177177
.subscribe({
178178
next: (response) => {
179179
this.snackBar.open('Agent started', 'Close', { duration: 3000 });
180-
this.router.navigate(['/ui/agent', response.data.agentId]).catch(console.error);
180+
this.router.navigate(['/ui/agents', response.data.agentId]).catch(console.error);
181181
},
182182
error: (error) => {
183183
this.snackBar.open(`Error ${error.message}`, 'Close', { duration: 3000 });

src/CONVENTIONS.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Backend code standards
2+
3+
## Test code standards
4+
5+
Unit test files should be in the same directory as the source file.
6+
7+
Any usage of chai-as-promised should use async/await
8+
```
9+
it('should work well with async/await', async () => {
10+
(await Promise.resolve(42)).should.equal(42)
11+
await Promise.reject(new Error()).should.be.rejectedWith(Error);
12+
});
13+
```
14+
15+
## Tool/function classes
16+
17+
Function classes with the @funcClass(__filename) must only have the default constructor.
18+
19+
Always use the Filesystem class in src/functions/storage/filesystem.ts to read/search/write to the local filesystem.

src/agent/cachingCodeGenAgentRunner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export async function runCachingCodegenAgent(agent: AgentContext): Promise<Agent
259259
.filter((pkg) => llmPythonCode.includes(`${pkg}.`) || pkg === 'json') // always need json for JsProxyEncoder
260260
.map((pkg) => `import ${pkg}\n`)
261261
.join();
262-
logger.info(`Allowed imports: ${pythonScript}`);
262+
263263
pythonScript += `
264264
from typing import Any, List, Dict, Tuple, Optional, Union
265265
from pyodide.ffi import JsProxy

src/agent/codeGenAgentRunner.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import { Span, SpanStatusCode } from '@opentelemetry/api';
33
import { PyodideInterface, loadPyodide } from 'pyodide';
44
import { runAgentCompleteHandler } from '#agent/agentCompletion';
55
import { AgentContext } from '#agent/agentContextTypes';
6-
import { AGENT_COMPLETED_NAME, AGENT_REQUEST_FEEDBACK, AGENT_SAVE_MEMORY_CONTENT_PARAM_NAME } from '#agent/agentFunctions';
6+
import {
7+
AGENT_COMPLETED_NAME,
8+
AGENT_COMPLETED_PARAM_NAME,
9+
AGENT_REQUEST_FEEDBACK,
10+
AGENT_SAVE_MEMORY_CONTENT_PARAM_NAME,
11+
REQUEST_FEEDBACK_PARAM_NAME,
12+
} from '#agent/agentFunctions';
713
import { buildFunctionCallHistoryPrompt, buildMemoryPrompt, buildToolStatePrompt, updateFunctionSchemas } from '#agent/agentPromptUtils';
814
import { AgentExecution, formatFunctionError, formatFunctionResult } from '#agent/agentRunner';
915
import { convertJsonToPythonDeclaration, extractPythonCode } from '#agent/codeGenAgentUtils';
@@ -215,7 +221,7 @@ export async function runCodeGenAgent(agent: AgentContext): Promise<AgentExecuti
215221
.filter((pkg) => llmPythonCode.includes(`${pkg}.`) || pkg === 'json') // always need json for JsProxyEncoder
216222
.map((pkg) => `import ${pkg}\n`)
217223
.join();
218-
logger.info(`Allowed imports: ${pythonScript}`);
224+
219225
pythonScript += `
220226
from typing import Any, List, Dict, Tuple, Optional, Union
221227
from pyodide.ffi import JsProxy
@@ -265,18 +271,18 @@ main()`.trim();
265271

266272
// Should force completed/requestFeedback to exit the script - throw a particular Error class
267273
if (lastFunctionCall.function_name === AGENT_COMPLETED_NAME) {
268-
logger.info('Task completed');
274+
logger.info(`Task completed: ${lastFunctionCall.parameters[AGENT_COMPLETED_PARAM_NAME]}`);
269275
agent.state = 'completed';
270276
completed = true;
271277
} else if (lastFunctionCall.function_name === AGENT_REQUEST_FEEDBACK) {
272-
logger.info('Feedback requested');
278+
logger.info(`Feedback requested: ${lastFunctionCall.parameters[REQUEST_FEEDBACK_PARAM_NAME]}`);
273279
agent.state = 'feedback';
274280
requestFeedback = true;
275281
} else {
276282
if (!anyFunctionCallErrors && !completed && !requestFeedback) agent.state = 'agent';
277283
}
278284
} catch (e) {
279-
logger.info(`Caught function error ${e.message}`);
285+
logger.info(e, `Caught function error ${e.message}`);
280286
functionErrorCount++;
281287
}
282288
// Function invocations are complete

src/agent/forceStopAgent.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { agentContext, agentContextStorage } from '#agent/agentContextLocalStorage';
2+
import { isExecuting } from '#agent/agentContextTypes';
3+
import { currentUser } from '#user/userService/userContext';
4+
import { appContext } from '../applicationContext';
5+
6+
const agentsToStop = new Set<string>();
7+
8+
/**
9+
* Terminates the execution of an agent as soon as possible.
10+
* @param agentId
11+
*/
12+
export async function forceStopAgent(agentId: string): Promise<void> {
13+
const agent = await appContext().agentStateService.load(agentId);
14+
if (!agent) throw new Error(`No agent with id ${agentId}`);
15+
if (!isExecuting(agent)) throw new Error(`Agent ${agentId} is not in an executing state`);
16+
if (agent.user.id !== currentUser().id) throw new Error('Cannot stop an agent owned by another user');
17+
18+
agentsToStop.add(agent.agentId);
19+
20+
// Reload the agent every 5 seconds for up to a minute and see if it's not in an executing state
21+
return new Promise((resolve, reject) => {
22+
const startTime = Date.now();
23+
const interval = setInterval(async () => {
24+
const updatedAgent = await appContext().agentStateService.load(agentId);
25+
// Agent should be in an error state if the checkForceStopped() function has been called in its execution
26+
if (!isExecuting(updatedAgent)) {
27+
clearInterval(interval);
28+
agentsToStop.delete(agent.agentId);
29+
resolve();
30+
} else if (Date.now() - startTime >= 60000) {
31+
// 1 minute timeout
32+
clearInterval(interval);
33+
agentsToStop.delete(agent.agentId);
34+
reject(new Error(`Agent ${agentId} did not stop executing within 1 minute`));
35+
}
36+
}, 5000);
37+
});
38+
}
39+
40+
/**
41+
* Checks if the current agent should be force stopped
42+
*/
43+
export function checkForceStopped(): void {
44+
const agent = agentContext();
45+
if (!agent) return;
46+
const agentId = typeof agent === 'string' ? agent : agent.agentId;
47+
48+
if (agentsToStop.has(agentId)) {
49+
agentsToStop.delete(agentId);
50+
throw new Error(`Agent ${agentId} has been force stopped by user ${currentUser().id}`);
51+
}
52+
}

src/cache/cacheRetry.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export function cacheRetry(options: Partial<CacheRetryOptions> = DEFAULTS) {
4040
const cacheRetryOptions = optionsWithDefaults(options);
4141

4242
const cacheService = appContext().functionCacheService;
43-
// console.log(this.constructor.name, methodName, args)
43+
4444
if (cacheRetryOptions.scope) {
4545
const cachedValue = await cacheService.getValue(cacheRetryOptions.scope, this.constructor.name, methodName, args);
4646

@@ -51,7 +51,7 @@ export function cacheRetry(options: Partial<CacheRetryOptions> = DEFAULTS) {
5151
}
5252

5353
for (let attempt = 1; attempt <= cacheRetryOptions.retries; attempt++) {
54-
logger.debug(`${this.constructor.name}${FUNC_SEP}${methodName} retry ${attempt - 1}`);
54+
if (attempt > 1) logger.debug(`${this.constructor.name}${FUNC_SEP}${methodName} retry ${attempt - 1}`);
5555
try {
5656
let result = originalMethod.apply(this, args);
5757
if (typeof result?.then === 'function') result = await result;

src/cli/gaia.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import { PublicWeb } from '#functions/web/web';
1111
import { LlmCall } from '#llm/llmCallService/llmCall';
1212
import { ClaudeLLMs } from '#llm/services/anthropic';
1313
import { Claude3_5_Sonnet_Vertex, ClaudeVertexLLMs } from '#llm/services/anthropic-vertex';
14-
import { groqLlama3_1_70B } from '#llm/services/groq';
14+
import { groqLlama3_3_70B } from '#llm/services/groq';
1515
import { Gemini_1_5_Flash } from '#llm/services/vertexai';
1616
import { logger } from '#o11y/logger';
1717
import { sleep } from '#utils/async-utils';
1818

19+
import { openAIo1 } from '#llm/services/openai';
1920
import { appContext, initFirestoreApplicationContext } from '../applicationContext';
2021

2122
const SYSTEM_PROMPT = `Finish your answer with the following template: FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, apply the above rules depending of whether the element to be put in the list is a number or a string.`;
@@ -92,9 +93,9 @@ async function answerGaiaQuestion(task: GaiaQuestion): Promise<GaiaResult> {
9293
// llms: ClaudeVertexLLMs(),
9394
llms: {
9495
easy: Gemini_1_5_Flash(),
95-
medium: groqLlama3_1_70B(),
96+
medium: groqLlama3_3_70B(),
9697
hard: Claude3_5_Sonnet_Vertex(),
97-
xhard: Claude3_5_Sonnet_Vertex(),
98+
xhard: openAIo1(),
9899
},
99100
agentName: `gaia-${task.task_id}`,
100101
type: 'codegen',

src/cli/gen.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { LlmFunctions } from '#agent/LlmFunctions';
77
import { agentContext, agentContextStorage, createContext } from '#agent/agentContextLocalStorage';
88
import { AgentContext, AgentLLMs } from '#agent/agentContextTypes';
99
import { LLM } from '#llm/llm';
10-
import { ClaudeLLMs } from '#llm/models/anthropic';
11-
import { Claude3_5_Sonnet_Vertex, ClaudeVertexLLMs } from '#llm/models/anthropic-vertex';
12-
import { GPT4oMini } from '#llm/models/openai';
10+
import { ClaudeLLMs } from '#llm/services/anthropic';
11+
import { Claude3_5_Sonnet_Vertex, ClaudeVertexLLMs } from '#llm/services/anthropic-vertex';
12+
import { GPT4oMini, openAIo1 } from '#llm/services/openai';
1313
import { currentUser } from '#user/userService/userContext';
1414
import { initFirestoreApplicationContext } from '../applicationContext';
1515
import { CliOptions, getLastRunAgentId, parseProcessArgs, saveAgentId } from './cli';

src/cli/query.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { ClaudeLLMs } from '#llm/services/anthropic';
99
import { ClaudeVertexLLMs } from '#llm/services/anthropic-vertex';
1010
import { cerebrasLlama3_70b } from '#llm/services/cerebras';
1111
import { deepseekChat } from '#llm/services/deepseek';
12-
import { groqLlama3_1_70B } from '#llm/services/groq';
1312
import { GPT4oMini, openAIo1, openAIo1mini } from '#llm/services/openai';
1413
import { Gemini_1_5_Flash } from '#llm/services/vertexai';
1514
import { codebaseQuery } from '#swe/discovery/codebaseQuery';

src/cli/swebench.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { PublicWeb } from '#functions/web/web';
1414
import { LlmCall } from '#llm/llmCallService/llmCall';
1515
import { ClaudeLLMs } from '#llm/services/anthropic';
1616
import { Claude3_5_Sonnet_Vertex, ClaudeVertexLLMs } from '#llm/services/anthropic-vertex';
17-
import { groqLlama3_1_70B } from '#llm/services/groq';
1817
import { Gemini_1_5_Flash } from '#llm/services/vertexai';
1918
import { logger } from '#o11y/logger';
2019
import { SWEBenchAgent, SWEInstance } from '#swe/SWEBenchAgent';

src/cli/watch.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ export function startWatcher() {
2424
const watcher = fs.watch(watchPath, { recursive: true }, async (event: WatchEventType, filename: string | null) => {
2525
// Early exit if filename is null
2626
if (!filename) return;
27-
console.log(filename);
28-
console.log(event);
27+
console.log(`${event} ${filename}`);
28+
2929
const filePath = path.join(process.cwd(), watchPath, filename);
3030
if (!fileExistsSync(filePath)) {
3131
logger.debug(`${filePath} doesn't exist`);
3232
return;
3333
}
34-
logger.info(`Checking ${filePath}`);
34+
console.log(`Checking ${filePath}`);
3535
try {
3636
const data = await fs.promises.readFile(filePath, 'utf-8');
3737

@@ -44,7 +44,7 @@ export function startWatcher() {
4444
const lines = data.split('\n');
4545

4646
// Find the index of the first line that starts with '//>>' and ends with '//'
47-
const index = lines.findIndex((line) => line.includes('//>>') && line.trim().endsWith('//'));
47+
const index = lines.findIndex((line) => line.includes('//>') && line.trim().endsWith('//'));
4848

4949
// Early exit if no matching lines are found
5050
if (index === -1) return;
@@ -54,7 +54,7 @@ export function startWatcher() {
5454
const indentation = line.match(/^\s*/)[0]; // Capture leading whitespace for indentation
5555
const requirements = line.trim().slice(3, -2).trim();
5656

57-
logger.info(requirements);
57+
logger.info(`Extracted requirements: ${requirements}`);
5858

5959
// Formulate the prompt
6060
const prompt = `You are to implement the TODO instructions on the line which starts with //>> and ends with //.\ni.e: ${requirements}`;

src/fastify/trace-init/trace-init.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { PinoInstrumentation } from './instrumentation';
1010

1111
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
1212
import { agentContextStorage } from '#agent/agentContextLocalStorage';
13+
import { checkForceStopped } from '#agent/forceStopAgent';
1314
import { setTracer } from '#o11y/trace';
1415

1516
let initialized = false;
@@ -104,9 +105,9 @@ function initTrace(): void {
104105
});
105106

106107
const tracer = trace.getTracer(traceServiceName);
107-
setTracer(tracer, agentContextStorage);
108+
setTracer(tracer, agentContextStorage, checkForceStopped);
108109
} else {
109-
setTracer(null, agentContextStorage);
110+
setTracer(null, agentContextStorage, checkForceStopped);
110111
}
111112
}
112113

src/functionRegistry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { GoogleCloud } from '#functions/cloud/google-cloud';
1+
import { GoogleCloud } from '#functions/cloud/google/google-cloud';
22
import { ImageGen } from '#functions/image';
33
import { Jira } from '#functions/jira';
44
import { GitHub } from '#functions/scm/github';

0 commit comments

Comments
 (0)