Created
June 9, 2025 16:03
-
-
Save dikamilo/337d872694834a85460200a1f5ad63b3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { readdir } from 'fs/promises' | |
import { promises as fs } from 'node:fs' | |
import { join } from 'path' | |
import { HeadquartersService, ReportResponse } from '@core/headquarters' | |
import { logger } from '@core/logger' | |
import { OpenAIModel, OpenAIService } from '@core/openai' | |
import { extractResultTag } from '@core/text' | |
import { questionSystemPrompt } from './agent.prompt' | |
import { QuestionResponse, State } from './types' | |
const DOCUMENTS_DIR_PATH = join(__dirname, 'files/facts') | |
export class AgentService { | |
private readonly agentKey: string | |
private readonly openAIService: OpenAIService | |
private readonly headquartersService: HeadquartersService | |
private state: State | |
private static readonly REPORT_TYPE = 'phone' | |
constructor(agentKey: string) { | |
this.agentKey = agentKey | |
this.openAIService = new OpenAIService() | |
this.headquartersService = new HeadquartersService() | |
this.state = { | |
facts: [], | |
transcriptions: {}, | |
questions: [], | |
answered: [], | |
config: { | |
max_steps: 10, | |
current_step: 0, | |
}, | |
} | |
} | |
async initialize() { | |
logger.info('⚙️ Initializing...') | |
logger.info('⚙️ Loading facts...') | |
this.state.facts = await this.loadFacts() | |
logger.info('⚙️ Retrieving questions and transcriptions...') | |
this.state.questions = await this.headquartersService.retrievePhonesQuestions(this.agentKey) | |
this.state.transcriptions = await this.headquartersService.retrievePhonesTranscriptions( | |
this.agentKey | |
) | |
} | |
private async processQuestions() { | |
for (const question of this.state.questions) { | |
logger.info('❓ Processing question: ', question.question) | |
const prompt = questionSystemPrompt( | |
this.state.facts, | |
this.state.transcriptions, | |
this.state.answered | |
) | |
const response = await this.openAIService.completion( | |
[ | |
{ | |
role: 'system', | |
content: prompt, | |
}, | |
{ role: 'user', content: JSON.stringify(question) }, | |
], | |
OpenAIModel.GPT41, | |
false, | |
false | |
) | |
const answer = JSON.parse( | |
extractResultTag(response.choices[0].message.content as string) | |
) as QuestionResponse | |
logger.info(` 🧠 Thinking: ${answer._thinking}`) | |
if (answer.answer || !answer.action?.name) { | |
logger.info(` 👉 Answer: ${answer.answer}`) | |
this.state.answered.push({ | |
...question, | |
answer: answer.answer || '', | |
}) | |
} | |
if (answer.action?.name) { | |
logger.info(' 🔧 Additional action required: ', answer.action.name) | |
const { parameters } = answer.action || {} | |
const apiResponse = await this.check_api_endpoint(parameters?.url, parameters?.password) | |
this.state.answered.push({ | |
...question, | |
answer: apiResponse, | |
}) | |
} | |
} | |
} | |
async correctQuestion(message: string) { | |
logger.info('😤 Correcting question...') | |
const prompt = questionSystemPrompt( | |
this.state.facts, | |
this.state.transcriptions, | |
this.state.answered | |
) | |
const response = await this.openAIService.completion( | |
[ | |
{ | |
role: 'system', | |
content: prompt, | |
}, | |
{ role: 'user', content: message }, | |
], | |
OpenAIModel.GPT41, | |
false, | |
false | |
) | |
const answer = JSON.parse( | |
extractResultTag(response.choices[0].message.content as string) | |
) as QuestionResponse | |
logger.info(` 🧠 Thinking: ${answer._thinking}`) | |
await Promise.all( | |
this.state.answered.map(async question => { | |
if (question.index === answer.question_index) { | |
if (answer.answer || !answer.action?.name) { | |
logger.info(` 👉 Answer: ${answer.answer}`) | |
question.answer = answer.answer! | |
} | |
if (answer.action?.name) { | |
logger.info(' 🔧 Additional action required: ', answer.action.name) | |
const { parameters } = answer.action || {} | |
question.answer = await this.check_api_endpoint(parameters?.url, parameters?.password) | |
} | |
} | |
return question | |
}) | |
) | |
} | |
async process() { | |
await this.processQuestions() | |
logger.info('✅ Questions processed successfully!') | |
for (let i = 0; i < this.state.config.max_steps; i++) { | |
this.state.config.current_step++ | |
const payload = this.state.answered.reduce( | |
(acc, item, index) => { | |
const key = String(index + 1).padStart(2, '0') as string | |
acc[key] = item.answer | |
return acc | |
}, | |
{} as { [index: string]: string } | |
) | |
const reportResponse = await this.headquartersService.report( | |
this.agentKey, | |
AgentService.REPORT_TYPE, | |
payload | |
) | |
if (reportResponse.code === 0) { | |
logger.info('✅ Report submitted successfully:', reportResponse.message) | |
break | |
} | |
logger.error(`🤦 Aw, shit! Here we go again: ${reportResponse.message}`) | |
await this.correctQuestion(reportResponse.message) | |
} | |
} | |
private async check_api_endpoint(url: string, password: string) { | |
const response = await fetch(url, { | |
method: 'POST', | |
headers: { | |
Accept: 'application/json', | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
password, | |
}), | |
}) | |
const result = (await response.json()) as ReportResponse | |
logger.info(` 🌍 Endpoint response: ${result.message}`) | |
return result.message | |
} | |
private async loadFacts() { | |
const files = await readdir(DOCUMENTS_DIR_PATH) | |
return await Promise.all( | |
files.map(async fileName => await this.loadFile(join(DOCUMENTS_DIR_PATH, fileName))) | |
) | |
} | |
private async loadFile(filePath: string) { | |
const fileData = await fs.readFile(filePath) | |
return fileData.toString() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment