Skip to content

Commit

Permalink
Merge pull request #24 from blockydevs/feat/linkedin-client-posts-gen…
Browse files Browse the repository at this point in the history
…erating-improvements

fix: client posts generating improvements
  • Loading branch information
KacperKoza343 authored Jan 22, 2025
2 parents 315a132 + a1a62c5 commit e9cb03f
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 25 deletions.
8 changes: 5 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,8 @@ INSTAGRAM_MAX_ACTIONS=1 # Maximum number of actions to process at once

# LinkedIn Configuration
LINKEDIN_ACCESS_TOKEN= # LinkedIn access token
LINKEDIN_POST_INTERVAL_MIN=60 # Optional, default: 60 minutes
LINKEDIN_POST_INTERVAL_MAX=120 # Optional, default: 120 minutes
LINKEDIN_API_URL=https://api.linkedin.com # Optional, default: https://api.linkedin.com
LINKEDIN_POST_INTERVAL_MIN=60 # Optional. Given in minutes. Default: 60 minutes
LINKEDIN_POST_INTERVAL_MAX=120 # Optional. Given in minutes. Default: 120 minutes
LINKEDIN_API_URL=https://api.linkedin.com # Optional. Default: https://api.linkedin.com
LINKEDIN_DRY_RUN=false # Optinal. Default: false. If set to true, the client will not publish posts, but can see the generated content in the console

2 changes: 1 addition & 1 deletion client/src/lib/info.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version": "0.1.8+build.1"}
{"version": "0.1.9-alpha.1"}
3 changes: 2 additions & 1 deletion packages/client-linkedin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
],
"dependencies": {
"@elizaos/core": "workspace:*",
"axios": "^1.7.9"
"axios": "^1.7.9",
"remove-markdown": "^0.6.0"
},
"devDependencies": {
"@vitest/coverage-v8": "1.1.3",
Expand Down
8 changes: 8 additions & 0 deletions packages/client-linkedin/src/helpers/validate-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IAgentRuntime } from "@elizaos/core";
const DEFAULT_LINKEDIN_API_URL = "https://api.linkedin.com";
const DEFAULT_LINKEDIN_POST_INTERVAL_MIN = 60;
const DEFAULT_LINKEDIN_POST_INTERVAL_MAX = 120;
const DEFAULT_LINKEDIN_DRY_RUN = false;

const parseNumber = (
val: string | number | null,
Expand Down Expand Up @@ -45,6 +46,11 @@ export const configSchema = z
LINKEDIN_API_URL: z
.union([z.string(), z.null(), z.undefined()])
.transform((val) => val ?? DEFAULT_LINKEDIN_API_URL),
LINKEDIN_DRY_RUN: z.union([
z.boolean(),
z.null(),
z.undefined(),
]).transform((val) => val ?? DEFAULT_LINKEDIN_DRY_RUN),
})
.superRefine((data, ctx) => {
if (data.LINKEDIN_POST_INTERVAL_MIN > data.LINKEDIN_POST_INTERVAL_MAX) {
Expand All @@ -65,13 +71,15 @@ export const validateConfig = (runtime: IAgentRuntime) => {
"LINKEDIN_POST_INTERVAL_MAX"
);
const LINKEDIN_API_URL = runtime.getSetting("LINKEDIN_API_URL");
const LINKEDIN_DRY_RUN = runtime.getSetting("LINKEDIN_DRY_RUN");

try {
const envs = configSchema.parse({
LINKEDIN_ACCESS_TOKEN,
LINKEDIN_POST_INTERVAL_MIN,
LINKEDIN_POST_INTERVAL_MAX,
LINKEDIN_API_URL,
LINKEDIN_DRY_RUN
});

return envs;
Expand Down
1 change: 1 addition & 0 deletions packages/client-linkedin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const LinkedInClient: Client = {
userInfoFetcher: new LinkedInUserInfoFetcher(axiosInstance),
runtime,
config: {
LINKEDIN_DRY_RUN: envs.LINKEDIN_DRY_RUN,
LINKEDIN_POST_INTERVAL_MIN: envs.LINKEDIN_POST_INTERVAL_MIN,
LINKEDIN_POST_INTERVAL_MAX: envs.LINKEDIN_POST_INTERVAL_MAX,
}
Expand Down
6 changes: 4 additions & 2 deletions packages/client-linkedin/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ export interface PostRequestWithMedia extends BasePostRequest {
export const API_VERSION_HEADER = "LinkedIn-Version";
export const API_VERSION = "202411";
export type Envs = z.infer<typeof configSchema>;
export type IntervalsConfig = Pick<
export type PublisherConfig = Pick<
Envs,
"LINKEDIN_POST_INTERVAL_MAX" | "LINKEDIN_POST_INTERVAL_MIN"
| "LINKEDIN_POST_INTERVAL_MAX"
| "LINKEDIN_POST_INTERVAL_MIN"
| "LINKEDIN_DRY_RUN"
>;
38 changes: 29 additions & 9 deletions packages/client-linkedin/src/services/LinkedInPostScheduler.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { AxiosInstance } from "axios";
import { LinkedInUserInfoFetcher } from "../repositories/LinkedinUserInfoFetcher";
import { PostContentCreator } from "./PostContentCreator";
import { IntervalsConfig } from "../interfaces";
import { LinkedInPostPublisher } from "../repositories/LinkedinPostPublisher";
import { elizaLogger, IAgentRuntime } from "@elizaos/core";
import { elizaLogger, IAgentRuntime, stringToUuid } from "@elizaos/core";
import { getRandomInteger } from "../helpers/get-random-integer";
import { LinkedInPostPublisher } from "../repositories/LinkedinPostPublisher";
import { PublisherConfig } from "../interfaces";

export class LinkedInPostScheduler {
constructor(
private runtime: IAgentRuntime,
private postPublisher: LinkedInPostPublisher,
private postContentCreator: PostContentCreator,
readonly userId: string,
readonly intervalsConfig: IntervalsConfig
readonly config: PublisherConfig
) {}

static async createPostScheduler({
Expand All @@ -24,7 +24,7 @@ export class LinkedInPostScheduler {
axiosInstance: AxiosInstance;
userInfoFetcher: LinkedInUserInfoFetcher;
runtime: IAgentRuntime;
config: IntervalsConfig;
config: PublisherConfig;
}) {
const userInfo = await userInfoFetcher.getUserInfo();

Expand All @@ -49,8 +49,8 @@ export class LinkedInPostScheduler {
}>("linkedin/" + this.userId + "/lastPost");

const lastPostTimestamp = lastPost?.timestamp ?? 0;
const minMinutes = this.intervalsConfig.LINKEDIN_POST_INTERVAL_MIN;
const maxMinutes = this.intervalsConfig.LINKEDIN_POST_INTERVAL_MAX;
const minMinutes = this.config.LINKEDIN_POST_INTERVAL_MIN;
const maxMinutes = this.config.LINKEDIN_POST_INTERVAL_MAX;

const randomMinutes = getRandomInteger(minMinutes, maxMinutes);
const delay = randomMinutes * 60 * 1000;
Expand All @@ -59,12 +59,32 @@ export class LinkedInPostScheduler {
const postText = await this.postContentCreator.createPostContent(
this.userId
);
await this.postPublisher.publishPost({ postText });

elizaLogger.log(`Generated post text`);
elizaLogger.log(postText);

if (!this.config.LINKEDIN_DRY_RUN) {
await this.postPublisher.publishPost({ postText });
elizaLogger.info("Published post");
} else {
elizaLogger.warn(
"Dry run is enabled. To publish posts set LINKEDIN_DRY_RUN to false"
);
}

this.runtime.messageManager.createMemory({
userId: this.runtime.agentId,
agentId: this.runtime.agentId,
roomId: stringToUuid("linkedin_generate_room-" + this.userId),
content: {
text: postText,
},
});

await this.runtime.cacheManager.set(
"linkedin/" + this.userId + "/lastPost",
{ timestamp: Date.now() }
);
elizaLogger.info("Published post");
}

setTimeout(() => {
Expand Down
24 changes: 22 additions & 2 deletions packages/client-linkedin/src/services/PostContentCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
ModelClass,
stringToUuid,
} from "@elizaos/core";
import { createLinkedinPostTemplate } from "../templates";
import removeMd from "remove-markdown";

export class PostContentCreator {
constructor(public runtime: IAgentRuntime) {}
Expand All @@ -25,13 +27,31 @@ export class PostContentCreator {

const context = composeContext({
state,
template: "post template",
template: createLinkedinPostTemplate,
});

return await generateText({
const text = await generateText({
runtime: this.runtime,
context,
modelClass: ModelClass.SMALL,
});

return this.removeMd(this.escapeSpecialCharacters(text));
}

removeMd(content: string) {
return removeMd(content);
}

escapeSpecialCharacters(content: string): string {
const escapedCharacters = content
.replace(/\(/g, "\\(")
.replace(/\)/g, "\\)")
.replace(/\[/g, "\\[")
.replace(/\]/g, "\\]")
.replace(/\{/g, "\\{")
.replace(/\}/g, "\\}");

return escapedCharacters;
}
}
34 changes: 34 additions & 0 deletions packages/client-linkedin/src/templates/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const createLinkedinPostTemplate = `
You are an AI assistant specialized in creating LinkedIn content. Given the {{state}} and LINKEDIN_POST {{topics}}, create a captivating and relevant LinkedIn post.
Content Structure:
1. Start with a specific fact or insight about the selected (randomly) topic from {{topics}} or somehow connected to one of them.
2. Explain why this concept is important for the target audience.
3. Provide a clear technical example related to the topic.
4. Share one specific implementation detail.
5. End with a technical discussion question.
6. Add 3-4 relevant technical hashtags at the end.
Style Guidelines:
- Maintain high technical accuracy.
- Use terminology appropriate for intermediate professionals.
- Keep a professional tone, focusing on knowledge sharing, not opinions.
- Length: 800-1200 characters.
- Avoid personal stories, career advice, or company-specific details.
- Don't use first-person narrative.
Formatting:
- Use normal text for rest of post
- Use emojis sparingly.
Hashtags:
- Include 3-4 relevant technical hashtags related to the topic (without spaces).
Avoid:
- Generic advice, motivational content, personal experiences, company-specific information, markdown formatting.
- Post similar to published post from {{state}}, first try to create post about topic not mentioned in {{state}}, if all topics are mentioned in {{state}} then create post about topic that is not mentioned in {{state}} but is related to one of the topics from {{state}}.
- Markdown formatting.
Important Note:
- Ensure that the reply strictly adheres to the formatting rules provided in the prompt, especially avoiding markdown formatting and using the specified text styles for bold, italics, and bold italics.
`;
19 changes: 12 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e9cb03f

Please sign in to comment.