Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement character loading from multiple paths and enhance API… #2365

Merged
merged 3 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 61 additions & 61 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ function mergeCharacters(base: Character, child: Character): Character {
async function loadCharacterFromUrl(url: string): Promise<Character> {
const response = await fetch(url);
const character = await response.json();
return jsonToCharacter(url, character);
return await jsonToCharacter(url, character);
}

async function jsonToCharacter(
Expand Down Expand Up @@ -246,11 +246,65 @@ async function loadCharacter(filePath: string): Promise<Character> {
return jsonToCharacter(filePath, character);
}

async function loadCharacterTryPath(characterPath: string): Promise<Character> {
let content: string | null = null;
let resolvedPath = "";

// Try different path resolutions in order
const pathsToTry = [
characterPath, // exact path as specified
path.resolve(process.cwd(), characterPath), // relative to cwd
path.resolve(process.cwd(), "agent", characterPath), // Add this
path.resolve(__dirname, characterPath), // relative to current script
path.resolve(__dirname, "characters", path.basename(characterPath)), // relative to agent/characters
path.resolve(__dirname, "../characters", path.basename(characterPath)), // relative to characters dir from agent
path.resolve(
__dirname,
"../../characters",
path.basename(characterPath)
), // relative to project root characters dir
];

elizaLogger.info(
"Trying paths:",
pathsToTry.map((p) => ({
path: p,
exists: fs.existsSync(p),
}))
);

for (const tryPath of pathsToTry) {
content = tryLoadFile(tryPath);
if (content !== null) {
resolvedPath = tryPath;
break;
}
}

if (content === null) {
elizaLogger.error(
`Error loading character from ${characterPath}: File not found in any of the expected locations`
);
elizaLogger.error("Tried the following paths:");
pathsToTry.forEach((p) => elizaLogger.error(` - ${p}`));
throw new Error(
`Error loading character from ${characterPath}: File not found in any of the expected locations`
);
}
try {
const character: Character = await loadCharacter(resolvedPath);
elizaLogger.info(`Successfully loaded character from: ${resolvedPath}`);
return character;
} catch (e) {
elizaLogger.error(`Error parsing character from ${resolvedPath}: ${e}`);
throw new Error(`Error parsing character from ${resolvedPath}: ${e}`);
}
}

function commaSeparatedStringToArray(commaSeparated: string): string[] {
return commaSeparated?.split(",").map(value => value.trim())
}


export async function loadCharacters(
charactersArg: string
): Promise<Character[]> {
Expand All @@ -259,68 +313,11 @@ export async function loadCharacters(

if (characterPaths?.length > 0) {
for (const characterPath of characterPaths) {
let content: string | null = null;
let resolvedPath = "";

// Try different path resolutions in order
const pathsToTry = [
characterPath, // exact path as specified
path.resolve(process.cwd(), characterPath), // relative to cwd
path.resolve(process.cwd(), "agent", characterPath), // Add this
path.resolve(__dirname, characterPath), // relative to current script
path.resolve(
__dirname,
"characters",
path.basename(characterPath)
), // relative to agent/characters
path.resolve(
__dirname,
"../characters",
path.basename(characterPath)
), // relative to characters dir from agent
path.resolve(
__dirname,
"../../characters",
path.basename(characterPath)
), // relative to project root characters dir
];

elizaLogger.info(
"Trying paths:",
pathsToTry.map((p) => ({
path: p,
exists: fs.existsSync(p),
}))
);

for (const tryPath of pathsToTry) {
content = tryLoadFile(tryPath);
if (content !== null) {
resolvedPath = tryPath;
break;
}
}

if (content === null) {
elizaLogger.error(
`Error loading character from ${characterPath}: File not found in any of the expected locations`
);
elizaLogger.error("Tried the following paths:");
pathsToTry.forEach((p) => elizaLogger.error(` - ${p}`));
process.exit(1);
}

try {
const character: Character = await loadCharacter(resolvedPath);

const character: Character =
await loadCharacterTryPath(characterPath);
loadedCharacters.push(character);
elizaLogger.info(
`Successfully loaded character from: ${resolvedPath}`
);
} catch (e) {
elizaLogger.error(
`Error parsing character from ${resolvedPath}: ${e}`
);
process.exit(1);
}
}
Expand Down Expand Up @@ -1154,6 +1151,9 @@ const startAgents = async () => {
return startAgent(character, directClient);
};

directClient.loadCharacterTryPath = loadCharacterTryPath;
directClient.jsonToCharacter = jsonToCharacter;

directClient.start(serverPort);

if (serverPort !== parseInt(settings.SERVER_PORT || "3000")) {
Expand Down
97 changes: 76 additions & 21 deletions packages/client-direct/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
UUID,
validateCharacterConfig,
ServiceType,
Character,
} from "@elizaos/core";

import { TeeLogQuery, TeeLogService } from "@elizaos/plugin-tee-log";
Expand Down Expand Up @@ -114,9 +115,8 @@ export function createApiRouter(
if (agent) {
agent.stop();
directClient.unregisterAgent(agent);
res.status(204).send();
}
else {
res.status(204).json({ success: true });
} else {
res.status(404).json({ error: "Agent not found" });
}
});
Expand Down Expand Up @@ -269,18 +269,20 @@ export function createApiRouter(

for (const agentRuntime of agents.values()) {
const teeLogService = agentRuntime
.getService<TeeLogService>(
ServiceType.TEE_LOG
)
.getInstance();
.getService<TeeLogService>(ServiceType.TEE_LOG)
.getInstance();

const agents = await teeLogService.getAllAgents();
allAgents.push(...agents)
allAgents.push(...agents);
}

const runtime: AgentRuntime = agents.values().next().value;
const teeLogService = runtime.getService<TeeLogService>(ServiceType.TEE_LOG).getInstance();
const attestation = await teeLogService.generateAttestation(JSON.stringify(allAgents));
const teeLogService = runtime
.getService<TeeLogService>(ServiceType.TEE_LOG)
.getInstance();
const attestation = await teeLogService.generateAttestation(
JSON.stringify(allAgents)
);
res.json({ agents: allAgents, attestation: attestation });
} catch (error) {
elizaLogger.error("Failed to get TEE agents:", error);
Expand All @@ -300,13 +302,13 @@ export function createApiRouter(
}

const teeLogService = agentRuntime
.getService<TeeLogService>(
ServiceType.TEE_LOG
)
.getInstance();
.getService<TeeLogService>(ServiceType.TEE_LOG)
.getInstance();

const teeAgent = await teeLogService.getAgent(agentId);
const attestation = await teeLogService.generateAttestation(JSON.stringify(teeAgent));
const attestation = await teeLogService.generateAttestation(
JSON.stringify(teeAgent)
);
res.json({ agent: teeAgent, attestation: attestation });
} catch (error) {
elizaLogger.error("Failed to get TEE agent:", error);
Expand Down Expand Up @@ -335,12 +337,16 @@ export function createApiRouter(
};
const agentRuntime: AgentRuntime = agents.values().next().value;
const teeLogService = agentRuntime
.getService<TeeLogService>(
ServiceType.TEE_LOG
)
.getService<TeeLogService>(ServiceType.TEE_LOG)
.getInstance();
const pageQuery = await teeLogService.getLogs(teeLogQuery, page, pageSize);
const attestation = await teeLogService.generateAttestation(JSON.stringify(pageQuery));
const pageQuery = await teeLogService.getLogs(
teeLogQuery,
page,
pageSize
);
const attestation = await teeLogService.generateAttestation(
JSON.stringify(pageQuery)
);
res.json({
logs: pageQuery,
attestation: attestation,
Expand All @@ -354,6 +360,55 @@ export function createApiRouter(
}
);

router.post("/agent/start", async (req, res) => {
const { characterPath, characterJson } = req.body;
console.log("characterPath:", characterPath);
console.log("characterJson:", characterJson);
try {
let character: Character;
if (characterJson) {
character = await directClient.jsonToCharacter(
characterPath,
characterJson
);
} else if (characterPath) {
character =
await directClient.loadCharacterTryPath(characterPath);
} else {
throw new Error("No character path or JSON provided");
}
await directClient.startAgent(character);
elizaLogger.log(`${character.name} started`);

res.json({
id: character.id,
character: character,
});
} catch (e) {
elizaLogger.error(`Error parsing character: ${e}`);
res.status(400).json({
error: e.message,
});
return;
}
});

router.post("/agents/:agentId/stop", async (req, res) => {
const agentId = req.params.agentId;
console.log("agentId", agentId);
const agent: AgentRuntime = agents.get(agentId);

// update character
if (agent) {
// stop agent
agent.stop();
directClient.unregisterAgent(agent);
// if it has a different name, the agentId will change
res.json({ success: true });
} else {
res.status(404).json({ error: "Agent not found" });
}
});

return router;
}

69 changes: 37 additions & 32 deletions packages/client-direct/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export class DirectClient {
private agents: Map<string, AgentRuntime>; // container management
private server: any; // Store server instance
public startAgent: Function; // Store startAgent functor
public loadCharacterTryPath: Function; // Store loadCharacterTryPath functor
public jsonToCharacter: Function; // Store jsonToCharacter functor

constructor() {
elizaLogger.log("DirectClient constructor");
Expand All @@ -136,7 +138,6 @@ export class DirectClient {
const apiRouter = createApiRouter(this.agents, this);
this.app.use(apiRouter);


const apiLogRouter = createVerifiableLogApiRouter(this.agents);
this.app.use(apiLogRouter);

Expand Down Expand Up @@ -555,38 +556,42 @@ export class DirectClient {
content: contentObj,
};

runtime.messageManager.createMemory(responseMessage).then(() => {
const messageId = stringToUuid(Date.now().toString());
const memory: Memory = {
id: messageId,
agentId: runtime.agentId,
userId,
roomId,
content,
createdAt: Date.now(),
};

// run evaluators (generally can be done in parallel with processActions)
// can an evaluator modify memory? it could but currently doesn't
runtime.evaluate(memory, state).then(() => {
// only need to call if responseMessage.content.action is set
if (contentObj.action) {
// pass memory (query) to any actions to call
runtime.processActions(
memory,
[responseMessage],
state,
async (_newMessages) => {
// FIXME: this is supposed override what the LLM said/decided
// but the promise doesn't make this possible
//message = newMessages;
return [memory];
}
); // 0.674s
}
resolve(true);
runtime.messageManager
.createMemory(responseMessage)
.then(() => {
const messageId = stringToUuid(
Date.now().toString()
);
const memory: Memory = {
id: messageId,
agentId: runtime.agentId,
userId,
roomId,
content,
createdAt: Date.now(),
};

// run evaluators (generally can be done in parallel with processActions)
// can an evaluator modify memory? it could but currently doesn't
runtime.evaluate(memory, state).then(() => {
// only need to call if responseMessage.content.action is set
if (contentObj.action) {
// pass memory (query) to any actions to call
runtime.processActions(
memory,
[responseMessage],
state,
async (_newMessages) => {
// FIXME: this is supposed override what the LLM said/decided
// but the promise doesn't make this possible
//message = newMessages;
return [memory];
}
); // 0.674s
}
resolve(true);
});
});
});
});
res.json({ response: hfOut });
}
Expand Down
Loading
Loading