From adfb7c2b49ba4ba691e89683afa700eabbb3388c Mon Sep 17 00:00:00 2001 From: sigoden Date: Sat, 22 Jun 2024 06:52:45 +0800 Subject: refactor: rename bot to agent (#44) --- .gitignore | 2 +- Argcfile.sh | 192 +++++++++++++++++++++++----------------------- README.md | 32 ++++---- agents/todo-js/index.yaml | 1 + agents/todo-js/tools.js | 68 ++++++++++++++++ agents/todo-py/index.yaml | 1 + agents/todo-py/tools.py | 62 +++++++++++++++ agents/todo-sh/index.yaml | 19 +++++ agents/todo-sh/tools.sh | 67 ++++++++++++++++ bots/todo-js/index.yaml | 1 - bots/todo-js/tools.js | 68 ---------------- bots/todo-py/index.yaml | 1 - bots/todo-py/tools.py | 62 --------------- bots/todo-sh/index.yaml | 19 ----- bots/todo-sh/tools.sh | 67 ---------------- scripts/run-agent.js | 107 ++++++++++++++++++++++++++ scripts/run-agent.py | 105 +++++++++++++++++++++++++ scripts/run-agent.sh | 80 +++++++++++++++++++ scripts/run-bot.js | 107 -------------------------- scripts/run-bot.py | 105 ------------------------- scripts/run-bot.sh | 80 ------------------- 21 files changed, 623 insertions(+), 623 deletions(-) create mode 120000 agents/todo-js/index.yaml create mode 100644 agents/todo-js/tools.js create mode 120000 agents/todo-py/index.yaml create mode 100644 agents/todo-py/tools.py create mode 100644 agents/todo-sh/index.yaml create mode 100755 agents/todo-sh/tools.sh delete mode 120000 bots/todo-js/index.yaml delete mode 100644 bots/todo-js/tools.js delete mode 120000 bots/todo-py/index.yaml delete mode 100644 bots/todo-py/tools.py delete mode 100644 bots/todo-sh/index.yaml delete mode 100755 bots/todo-sh/tools.sh create mode 100755 scripts/run-agent.js create mode 100755 scripts/run-agent.py create mode 100755 scripts/run-agent.sh delete mode 100755 scripts/run-bot.js delete mode 100755 scripts/run-bot.py delete mode 100755 scripts/run-bot.sh diff --git a/.gitignore b/.gitignore index 29f8666..924751c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ /tmp functions.txt tools.txt -bots.txt +agents.txt functions.json /bin /cache diff --git a/Argcfile.sh b/Argcfile.sh index 952921b..7f0a0d2 100644 --- a/Argcfile.sh +++ b/Argcfile.sh @@ -32,17 +32,17 @@ run@tool() { "$BIN_DIR/$argc_cmd$ext" "$argc_json" } -# @cmd Run the bot -# @alias bot:run -# @arg cmd![`_choice_bot`] The bot command -# @arg action![`_choice_bot_action`] The bot action +# @cmd Run the agent +# @alias agent:run +# @arg cmd![`_choice_agent`] The agent command +# @arg action![`_choice_agent_action`] The agent action # @arg json The json data -run@bot() { +run@agent() { if _is_win; then ext=".cmd" fi if [[ -z "$argc_json" ]]; then - functions_path="bots/$argc_cmd/functions.json" + functions_path="agents/$argc_cmd/functions.json" if [[ -f "$functions_path" ]]; then declaration="$(jq --arg name "$argc_action" '.[] | select(.name == $name)' "$functions_path")" if [[ -n "$declaration" ]]; then @@ -63,10 +63,10 @@ build() { else echo 'Skipped building tools sine tools.txt is missing' fi - if [[ -f bots.txt ]]; then - argc build@bot + if [[ -f agents.txt ]]; then + argc build@agent else - echo 'Skipped building bots sine bots.txt is missing' + echo 'Skipped building agents sine agents.txt is missing' fi } @@ -180,33 +180,33 @@ generate-declarations@tool() { "$cmd" "scripts/build-declarations.$lang" "tools/$1" } -# @cmd Build bots -# @alias bot:build -# @option --names-file=bots.txt Path to a file containing bot filenames, one per line. +# @cmd Build agents +# @alias agent:build +# @option --names-file=agents.txt Path to a file containing agent filenames, one per line. # Example: # hackernews # spotify -# @arg bots*[`_choice_bot`] The bot filenames -build@bot() { - if [[ "${#argc_bots[@]}" -gt 0 ]]; then +# @arg agents*[`_choice_agent`] The agent filenames +build@agent() { + if [[ "${#argc_agents[@]}" -gt 0 ]]; then mkdir -p "$TMP_DIR" - argc_names_file="$TMP_DIR/bots.txt" - printf "%s\n" "${argc_bots[@]}" > "$argc_names_file" + argc_names_file="$TMP_DIR/agents.txt" + printf "%s\n" "${argc_agents[@]}" > "$argc_names_file" else - argc clean@bot + argc clean@agent fi - argc build-declarations@bot --names-file "${argc_names_file}" - argc build-bin@bot --names-file "${argc_names_file}" + argc build-declarations@agent --names-file "${argc_names_file}" + argc build-bin@agent --names-file "${argc_names_file}" } -# @cmd Build bots to bin -# @alias bot:build-bin -# @option --names-file=bots.txt Path to a file containing bot dirs, one per line. -# @arg bots*[`_choice_bot`] The bot names -build-bin@bot() { +# @cmd Build agents to bin +# @alias agent:build-bin +# @option --names-file=agents.txt Path to a file containing agent dirs, one per line. +# @arg agents*[`_choice_agent`] The agent names +build-bin@agent() { mkdir -p "$BIN_DIR" - if [[ "${#argc_bots[@]}" -gt 0 ]]; then - names=("${argc_bots[@]}" ) + if [[ "${#argc_agents[@]}" -gt 0 ]]; then + names=("${argc_agents[@]}" ) elif [[ -f "$argc_names_file" ]]; then names=($(cat "$argc_names_file")) if [[ "${#names[@]}" -gt 0 ]]; then @@ -214,88 +214,88 @@ build-bin@bot() { fi fi if [[ -z "$names" ]]; then - _die "error: not input bots, not found '$argc_names_file', please create it add some tools." + _die "error: not input agents, not found '$argc_names_file', please create it add some tools." fi - not_found_bots=() + not_found_agents=() for name in "${names[@]}"; do - bot_dir="bots/$name" + agent_dir="agents/$name" found=false for item in "${LANG_CMDS[@]}"; do lang="${item%:*}" - bot_tools_file="$bot_dir/tools.$lang" - if [[ -f "$bot_tools_file" ]]; then + agent_tools_file="$agent_dir/tools.$lang" + if [[ -f "$agent_tools_file" ]]; then found=true if _is_win; then bin_file="$BIN_DIR/$name.cmd" - _build_win_shim bot $lang > "$bin_file" + _build_win_shim agent $lang > "$bin_file" else bin_file="$BIN_DIR/$name" - ln -s -f "$PWD/scripts/run-bot.$lang" "$bin_file" + ln -s -f "$PWD/scripts/run-agent.$lang" "$bin_file" fi - echo "Build bot $name" + echo "Build agent $name" fi done if [[ "$found" = "false" ]]; then - not_found_bots+=("$name") + not_found_agents+=("$name") fi done - if [[ -n "$not_found_bots" ]]; then - _die "error: not found bots: ${not_found_bots[*]}" + if [[ -n "$not_found_agents" ]]; then + _die "error: not found agents: ${not_found_agents[*]}" fi } -# @cmd Build bots function declarations file -# @alias bot:build-declarations -# @option --names-file=bots.txt Path to a file containing bot dirs, one per line. -# @arg bots*[`_choice_bot`] The tool filenames -build-declarations@bot() { - if [[ "${#argc_bots[@]}" -gt 0 ]]; then - names=("${argc_bots[@]}" ) +# @cmd Build agents function declarations file +# @alias agent:build-declarations +# @option --names-file=agents.txt Path to a file containing agent dirs, one per line. +# @arg agents*[`_choice_agent`] The tool filenames +build-declarations@agent() { + if [[ "${#argc_agents[@]}" -gt 0 ]]; then + names=("${argc_agents[@]}" ) elif [[ -f "$argc_names_file" ]]; then names=($(cat "$argc_names_file")) fi if [[ -z "$names" ]]; then - _die "error: not input bots, not found '$argc_names_file', please create it add some tools." + _die "error: not input agents, not found '$argc_names_file', please create it add some tools." fi - not_found_bots=() - build_failed_bots=() + not_found_agents=() + build_failed_agents=() for name in "${names[@]}"; do - bot_dir="bots/$name" + agent_dir="agents/$name" build_ok=false found=false for item in "${LANG_CMDS[@]}"; do lang="${item%:*}" - bot_tools_file="$bot_dir/tools.$lang" - if [[ -f "$bot_tools_file" ]]; then + agent_tools_file="$agent_dir/tools.$lang" + if [[ -f "$agent_tools_file" ]]; then found=true - json_data="$(generate-declarations@bot "$name")" || { - build_failed_bots+=("$name") + json_data="$(generate-declarations@agent "$name")" || { + build_failed_agents+=("$name") } - declarations_file="$bot_dir/functions.json" + declarations_file="$agent_dir/functions.json" echo "Build $declarations_file" echo "$json_data" > "$declarations_file" fi done if [[ "$found" == "false" ]]; then - not_found_bots+=("$name") + not_found_agents+=("$name") fi done - if [[ -n "$not_found_bots" ]]; then - _die "error: not found bots: ${not_found_bots[*]}" + if [[ -n "$not_found_agents" ]]; then + _die "error: not found agents: ${not_found_agents[*]}" fi - if [[ -n "$build_failed_bots" ]]; then - _die "error: invalid bots: ${build_failed_bots[*]}" + if [[ -n "$build_failed_agents" ]]; then + _die "error: invalid agents: ${build_failed_agents[*]}" fi } -# @cmd Generate function declarations for the bot -# @alias bot:generate-declarations +# @cmd Generate function declarations for the agent +# @alias agent:generate-declarations # @flag --oneline Summary JSON in one line -# @arg bot![`_choice_bot`] The bot name -generate-declarations@bot() { - tools_path="$(_get_bot_tools_path "$1")" +# @arg agent![`_choice_agent`] The agent name +generate-declarations@agent() { + tools_path="$(_get_agent_tools_path "$1")" if [[ -z "$tools_path" ]]; then - _die "error: no found entry file at bots/$1/tools." + _die "error: no found entry file at agents/$1/tools." fi lang="${tools_path##*.}" cmd="$(_lang_to_cmd "$lang")" @@ -315,18 +315,18 @@ list@tool() { _choice_tool } -# @cmd List bots which can be put into bots.txt -# @alias bot:list +# @cmd List agents which can be put into agents.txt +# @alias agent:list # Examples: -# argc list-bots > bots.txt -list@bot() { - _choice_bot +# argc list-agents > agents.txt +list@agent() { + _choice_agent } # @cmd Test the project test() { test@tool - test@bot + test@agent } # @cmd Test tools @@ -395,20 +395,20 @@ test-demo-tools() { done } -# @cmd Test bots -# @alias bot:test -test@bot() { +# @cmd Test agents +# @alias agent:test +test@agent() { tmp_dir="cache/tmp" mkdir -p "$tmp_dir" - names_file="$tmp_dir/bots.txt" - argc list@bot > "$names_file" - argc build@bot --names-file "$names_file" - test-todo-bots + names_file="$tmp_dir/agents.txt" + argc list@agent > "$names_file" + argc build@agent --names-file "$names_file" + test-todo-agents } -# @cmd Test todo-* bots -# @alias bot:test-todo -test-todo-bots() { +# @cmd Test todo-* agents +# @alias agent:test-todo +test-todo-agents() { if _is_win; then ext=".cmd" fi @@ -423,11 +423,11 @@ test-todo-bots() { cmd="${item#*:}" if command -v "$cmd" &> /dev/null; then lang="${item%:*}" - bot_name="todo-$lang" - rm -rf "cache/$bot_name/todos.json" + agent_name="todo-$lang" + rm -rf "cache/$agent_name/todos.json" for test_case in "${test_cases[@]}"; do IFS='#' read -r action data <<<"${test_case}" - cmd_path="$BIN_DIR/$bot_name$ext" + cmd_path="$BIN_DIR/$agent_name$ext" echo "Test $cmd_path: " "$cmd_path" "$action" "$data" done @@ -443,11 +443,11 @@ clean@tool() { rm -rf functions.json } -# @cmd Clean bots -# @alias bot:clean -clean@bot() { - _choice_bot | xargs -I{} rm -rf "$BIN_DIR/{}" - _choice_bot | xargs -I{} rm -rf bots/{}/functions.json +# @cmd Clean agents +# @alias agent:clean +clean@agent() { + _choice_agent | xargs -I{} rm -rf "$BIN_DIR/{}" + _choice_agent | xargs -I{} rm -rf agents/{}/functions.json } # @cmd Install this repo to aichat functions_dir @@ -500,12 +500,12 @@ _lang_to_cmd() { done } -_get_bot_tools_path() { +_get_agent_tools_path() { name="$1" for item in "${LANG_CMDS[@]}"; do lang="${item%:*}" - entry_file="bots/$name/tools.$lang" - if [[ -f "bots/$name/tools.$lang" ]]; then + entry_file="agents/$name/tools.$lang" + if [[ -f "agents/$name/tools.$lang" ]]; then echo "$entry_file" fi done @@ -581,17 +581,17 @@ _choice_tool() { done } -_choice_bot() { - ls -1 bots +_choice_agent() { + ls -1 agents } -_choice_bot_action() { +_choice_agent_action() { if [[ "$ARGC_COMPGEN" -eq 1 ]]; then expr="s/: /\t/" else expr="s/:.*//" fi - argc generate-declarations@bot "$1" --oneline | sed "$expr" + argc generate-declarations@agent "$1" --oneline | sed "$expr" } _choice_cmd() { diff --git a/README.md b/README.md index 20f854e..9690a69 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Make sure you have the following tools installed: git clone https://github.com/sigoden/llm-functions ``` -**2. Build tools and bots:** +**2. Build tools and agents:** - Create a `./tools.txt` file with each tool filename on a new line. @@ -26,14 +26,14 @@ get_current_weather.sh may_execute_py_code.py ``` -- Create a `./bots.txt` file with each bot name on a new line. +- Create a `./agents.txt` file with each agent name on a new line. ``` todo-sh hackernews ``` -- Run `argc build` to build functions declarations files (`functions.json`) and binaries (`./bin`) for tools and bots. +- Run `argc build` to build functions declarations files (`functions.json`) and binaries (`./bin`) for tools and agents. **3. Configure your AIChat:** @@ -63,7 +63,7 @@ Now you can interact with your LLM using natural language prompts that trigger y ![execute-type-showcase](https://github.com/sigoden/llm-functions/assets/4012553/1dbc345f-daf9-4d65-a49f-3df8c7df1727) -![bot-showcase](https://github.com/sigoden/llm-functions/assets/4012553/b4411eeb-d79c-4245-8ec2-dd424ba25621) +![agent-showcase](https://github.com/sigoden/llm-functions/assets/4012553/b4411eeb-d79c-4245-8ec2-dd424ba25621) ## Writing Your Own Tools @@ -122,32 +122,32 @@ def main(code: str): ``` -## Writing Bots +## Writing Agents -Bot = Prompt + Tools (Function Callings) + Knowndge (RAG). It's also known as OpenAI's GPTs. +Agent = Prompt + Tools (Function Callings) + Knowndge (RAG). It's also known as OpenAI's GPTs. -The bot has the following folder structure: +The agent has the following folder structure: ``` -└── bots - └── mybot +└── agents + └── myagent ├── embeddings/ # Contains RAG files for knownledge ├── functions.json # Function declarations file (Auto-generated) - ├── index.yaml # Bot definition file - └── tools.{sh,js,py} # Bot tools script + ├── index.yaml # Agent definition file + └── tools.{sh,js,py} # Agent tools script ``` -The bot definition file (`index.yaml`) defines crucial aspects of your bot: +The agent definition file (`index.yaml`) defines crucial aspects of your agent: ```yaml -name: TestBot -description: This is test bot +name: TestAgent +description: This is test agent version: v0.1.0 -instructions: You are a test bot to ... +instructions: You are a test agent to ... conversation_starters: - What can you do? ``` -Refer to `./bots/todo-{sh,js,py}` for examples of how to implement a bot. +Refer to `./agents/todo-{sh,js,py}` for examples of how to implement a agent. ## License diff --git a/agents/todo-js/index.yaml b/agents/todo-js/index.yaml new file mode 120000 index 0000000..0d19c11 --- /dev/null +++ b/agents/todo-js/index.yaml @@ -0,0 +1 @@ +../todo-sh/index.yaml \ No newline at end of file diff --git a/agents/todo-js/tools.js b/agents/todo-js/tools.js new file mode 100644 index 0000000..cef4c8f --- /dev/null +++ b/agents/todo-js/tools.js @@ -0,0 +1,68 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * Add a new todo item + * @typedef {Object} AddTodoArgs + * @property {string} desc - The task description + * @param {AddTodoArgs} args + */ +exports.add_todo = function addTodo(args) { + const todosFile = _getTodosFile(); + if (fs.existsSync(todosFile)) { + const num = JSON.parse(fs.readFileSync(todosFile)).reduce((max, item) => Math.max(max, item.id), 0) + 1; + const data = fs.readFileSync(todosFile); + fs.writeFileSync(todosFile, JSON.stringify([...JSON.parse(data), { id: num, desc: args.desc }])); + console.log(`Successfully added todo id=${num}`); + } else { + fs.writeFileSync(todosFile, JSON.stringify([{ id: 1, desc: args.desc }])); + console.log('Successfully added todo id=1'); + } +} + +/** + * Delete an existing todo item + * @typedef {Object} DelTodoArgs + * @property {number} id - The task id + * @param {DelTodoArgs} args + */ +exports.del_todo = function delTodo(args) { + const todosFile = _getTodosFile(); + if (fs.existsSync(todosFile)) { + const data = fs.readFileSync(todosFile); + const newData = JSON.parse(data).filter(item => item.id !== args.id); + fs.writeFileSync(todosFile, JSON.stringify(newData)); + console.log(`Successfully deleted todo id=${args.id}`); + } else { + console.log('Empty todo list'); + } +} + +/** + * Display the current todo list in json format. + */ +exports.list_todos = function listTodos() { + const todosFile = _getTodosFile(); + if (fs.existsSync(todosFile)) { + console.log(fs.readFileSync(todosFile, "utf8")); + } else { + console.log("[]"); + } +} + +/** + * Delete the entire todo list. + */ +exports.clear_todos = function clearTodos() { + const todosFile = _getTodosFile(); + fs.unlinkSync(todosFile) + console.log("Successfully deleted entry todo list"); +} + +function _getTodosFile() { + const cacheDir = process.env.LLM_AGENT_CACHE_DIR || '/tmp'; + if (!fs.existsSync(cacheDir)) { + fs.mkdirSync(cacheDir, { recursive: true }); + } + return path.join(cacheDir, 'todos.json'); +} diff --git a/agents/todo-py/index.yaml b/agents/todo-py/index.yaml new file mode 120000 index 0000000..0d19c11 --- /dev/null +++ b/agents/todo-py/index.yaml @@ -0,0 +1 @@ +../todo-sh/index.yaml \ No newline at end of file diff --git a/agents/todo-py/tools.py b/agents/todo-py/tools.py new file mode 100644 index 0000000..0aa5f4b --- /dev/null +++ b/agents/todo-py/tools.py @@ -0,0 +1,62 @@ +import json +import sys +import os +from json import JSONDecodeError + + +def add_todo(desc: str): + """Add a new todo item + Args: + desc: The task description + """ + todos_file = _get_todos_file() + try: + with open(todos_file, "r") as f: + data = json.load(f) + except (FileNotFoundError, JSONDecodeError): + data = [] + num = max([item["id"] for item in data] + [0]) + 1 + data.append({"id": num, "desc": desc}) + with open(todos_file, "w") as f: + json.dump(data, f) + print(f"Successfully added todo id={num}") + + +def del_todo(id: int): + """Delete an existing todo item + Args: + id: The task id + """ + todos_file = _get_todos_file() + try: + with open(todos_file, "r") as f: + data = json.load(f) + except (FileNotFoundError, JSONDecodeError): + print("Empty todo list") + return + data = [item for item in data if item["id"] != id] + with open(todos_file, "w") as f: + json.dump(data, f) + print(f"Successfully deleted todo id={id}") + + +def list_todos(): + """Display the current todo list in json format.""" + todos_file = _get_todos_file() + try: + with open(todos_file, "r") as f: + print(f.read()) + except FileNotFoundError: + print("[]") + + +def clear_todos(): + """Delete the entire todo list.""" + os.remove(_get_todos_file()) + + +def _get_todos_file() -> str: + cache_dir=os.environ.get("LLM_AGENT_CACHE_DIR", "/tmp") + if not os.path.exists(cache_dir): + os.makedirs(cache_dir, exist_ok=True) + return os.path.join(cache_dir, "todos.json") diff --git a/agents/todo-sh/index.yaml b/agents/todo-sh/index.yaml new file mode 100644 index 0000000..490280e --- /dev/null +++ b/agents/todo-sh/index.yaml @@ -0,0 +1,19 @@ +name: Todo +description: A helpful ai agent that manages a todo list. +instructions: | + You will be provided with a list of todos. + Users can interact with you using the following commands: + * add_todo: Add a todo to the list. + * rm_todo: Remove a todo from the list. + * list_todos: Display the current todo list. + * clear_todos: Delete the entire todo list. + Based on the interaction, ensure that you provide appropriate confirmations or errors for the requested operation. For example: + - Confirmations: "Todo item added successfully!", "Todo item removed successfully!", "All todo items deleted!" + - Errors: "Cannot add todo item, missing description.", "Todo item with id {id} not found.", "No todo items to delete." + Make sure you understand the user request properly before performing any action. If unsure, ask clarifying questions like "Do you want to remove all todos or just a specific one?" +conversation_starters: + - "Add a new todo item 'Finish report'." + - "Remove the todo item with id=2." + - "Delete all my todos." + - "What todos do I have pending?" + - "How can I remove a specific todo item?" \ No newline at end of file diff --git a/agents/todo-sh/tools.sh b/agents/todo-sh/tools.sh new file mode 100755 index 0000000..29454aa --- /dev/null +++ b/agents/todo-sh/tools.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -e + +# @cmd Add a new todo item +# @option --desc! The task description +add_todo() { + todos_file="$(_get_todos_file)" + if [[ -f "$todos_file" ]]; then + num="$(cat "$todos_file" | jq '[.[].id] | max + 1')" + data="$(cat "$todos_file")" + else + num=1 + data="[]" + fi + echo "$data" | \ + jq --arg new_id $num \ + --arg new_desc "$argc_desc" \ + '. += [{"id": $new_id | tonumber, "desc": $new_desc}]' \ + > "$todos_file" + echo "Successfully added todo id=$num" +} + +# @cmd Delete an existing todo item +# @option --id! The task id +del_todo() { + todos_file="$(_get_todos_file)" + if [[ -f "$todos_file" ]]; then + data="$(cat "$todos_file")" + echo "$data" | \ + jq --arg id $argc_id '[.[] | select(.id != ($id | tonumber))]' \ + > "$todos_file" + echo "Successfully deleted todo id=$argc_id" + else + echo "Empty todo list" + fi +} + +# @cmd Display the current todo list in json format. +list_todos() { + todos_file="$(_get_todos_file)" + if [[ -f "$todos_file" ]]; then + cat "$todos_file" + else + echo '[]' + fi +} + +# @cmd Delete the entire todo list. +clear_todos() { + todos_file="$(_get_todos_file)" + if [[ -f "$todos_file" ]]; then + rm -rf "$todos_file" + fi + echo "Successfully deleted entry todo list" +} + +_argc_before() { + todos_file="$(_get_todos_file)" + mkdir -p "$(dirname "$todos_file")" +} + +_get_todos_file() { + echo "${LLM_AGENT_CACHE_DIR:-/tmp}/todos.json" +} + +# See more details at https://github.com/sigoden/argc +eval "$(argc --argc-eval "$0" "$@")" diff --git a/bots/todo-js/index.yaml b/bots/todo-js/index.yaml deleted file mode 120000 index 0d19c11..0000000 --- a/bots/todo-js/index.yaml +++ /dev/null @@ -1 +0,0 @@ -../todo-sh/index.yaml \ No newline at end of file diff --git a/bots/todo-js/tools.js b/bots/todo-js/tools.js deleted file mode 100644 index 0c9db49..0000000 --- a/bots/todo-js/tools.js +++ /dev/null @@ -1,68 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -/** - * Add a new todo item - * @typedef {Object} AddTodoArgs - * @property {string} desc - The task description - * @param {AddTodoArgs} args - */ -exports.add_todo = function addTodo(args) { - const todosFile = _getTodosFile(); - if (fs.existsSync(todosFile)) { - const num = JSON.parse(fs.readFileSync(todosFile)).reduce((max, item) => Math.max(max, item.id), 0) + 1; - const data = fs.readFileSync(todosFile); - fs.writeFileSync(todosFile, JSON.stringify([...JSON.parse(data), { id: num, desc: args.desc }])); - console.log(`Successfully added todo id=${num}`); - } else { - fs.writeFileSync(todosFile, JSON.stringify([{ id: 1, desc: args.desc }])); - console.log('Successfully added todo id=1'); - } -} - -/** - * Delete an existing todo item - * @typedef {Object} DelTodoArgs - * @property {number} id - The task id - * @param {DelTodoArgs} args - */ -exports.del_todo = function delTodo(args) { - const todosFile = _getTodosFile(); - if (fs.existsSync(todosFile)) { - const data = fs.readFileSync(todosFile); - const newData = JSON.parse(data).filter(item => item.id !== args.id); - fs.writeFileSync(todosFile, JSON.stringify(newData)); - console.log(`Successfully deleted todo id=${args.id}`); - } else { - console.log('Empty todo list'); - } -} - -/** - * Display the current todo list in json format. - */ -exports.list_todos = function listTodos() { - const todosFile = _getTodosFile(); - if (fs.existsSync(todosFile)) { - console.log(fs.readFileSync(todosFile, "utf8")); - } else { - console.log("[]"); - } -} - -/** - * Delete the entire todo list. - */ -exports.clear_todos = function clearTodos() { - const todosFile = _getTodosFile(); - fs.unlinkSync(todosFile) - console.log("Successfully deleted entry todo list"); -} - -function _getTodosFile() { - const cacheDir = process.env.LLM_BOT_CACHE_DIR || '/tmp'; - if (!fs.existsSync(cacheDir)) { - fs.mkdirSync(cacheDir, { recursive: true }); - } - return path.join(cacheDir, 'todos.json'); -} diff --git a/bots/todo-py/index.yaml b/bots/todo-py/index.yaml deleted file mode 120000 index 0d19c11..0000000 --- a/bots/todo-py/index.yaml +++ /dev/null @@ -1 +0,0 @@ -../todo-sh/index.yaml \ No newline at end of file diff --git a/bots/todo-py/tools.py b/bots/todo-py/tools.py deleted file mode 100644 index a531b51..0000000 --- a/bots/todo-py/tools.py +++ /dev/null @@ -1,62 +0,0 @@ -import json -import sys -import os -from json import JSONDecodeError - - -def add_todo(desc: str): - """Add a new todo item - Args: - desc: The task description - """ - todos_file = _get_todos_file() - try: - with open(todos_file, "r") as f: - data = json.load(f) - except (FileNotFoundError, JSONDecodeError): - data = [] - num = max([item["id"] for item in data] + [0]) + 1 - data.append({"id": num, "desc": desc}) - with open(todos_file, "w") as f: - json.dump(data, f) - print(f"Successfully added todo id={num}") - - -def del_todo(id: int): - """Delete an existing todo item - Args: - id: The task id - """ - todos_file = _get_todos_file() - try: - with open(todos_file, "r") as f: - data = json.load(f) - except (FileNotFoundError, JSONDecodeError): - print("Empty todo list") - return - data = [item for item in data if item["id"] != id] - with open(todos_file, "w") as f: - json.dump(data, f) - print(f"Successfully deleted todo id={id}") - - -def list_todos(): - """Display the current todo list in json format.""" - todos_file = _get_todos_file() - try: - with open(todos_file, "r") as f: - print(f.read()) - except FileNotFoundError: - print("[]") - - -def clear_todos(): - """Delete the entire todo list.""" - os.remove(_get_todos_file()) - - -def _get_todos_file() -> str: - cache_dir=os.environ.get("LLM_BOT_CACHE_DIR", "/tmp") - if not os.path.exists(cache_dir): - os.makedirs(cache_dir, exist_ok=True) - return os.path.join(cache_dir, "todos.json") diff --git a/bots/todo-sh/index.yaml b/bots/todo-sh/index.yaml deleted file mode 100644 index a3c8eb1..0000000 --- a/bots/todo-sh/index.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: TodoBot -description: Your name is TodoBot and you are a helpful chatbot that manages a todo list. -instructions: | - You will be provided with a list of todos. - Users can interact with you using the following commands: - * add_todo: Add a todo to the list. - * rm_todo: Remove a todo from the list. - * list_todos: Display the current todo list. - * clear_todos: Delete the entire todo list. - Based on the interaction, ensure that you provide appropriate confirmations or errors for the requested operation. For example: - - Confirmations: "Todo item added successfully!", "Todo item removed successfully!", "All todo items deleted!" - - Errors: "Cannot add todo item, missing description.", "Todo item with id {id} not found.", "No todo items to delete." - Make sure you understand the user request properly before performing any action. If unsure, ask clarifying questions like "Do you want to remove all todos or just a specific one?" -conversation_starters: - - "Add a new todo item 'Finish report'." - - "Remove the todo item with id=2." - - "Delete all my todos." - - "What todos do I have pending?" - - "How can I remove a specific todo item?" \ No newline at end of file diff --git a/bots/todo-sh/tools.sh b/bots/todo-sh/tools.sh deleted file mode 100755 index e387d75..0000000 --- a/bots/todo-sh/tools.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash -set -e - -# @cmd Add a new todo item -# @option --desc! The task description -add_todo() { - todos_file="$(_get_todos_file)" - if [[ -f "$todos_file" ]]; then - num="$(cat "$todos_file" | jq '[.[].id] | max + 1')" - data="$(cat "$todos_file")" - else - num=1 - data="[]" - fi - echo "$data" | \ - jq --arg new_id $num \ - --arg new_desc "$argc_desc" \ - '. += [{"id": $new_id | tonumber, "desc": $new_desc}]' \ - > "$todos_file" - echo "Successfully added todo id=$num" -} - -# @cmd Delete an existing todo item -# @option --id! The task id -del_todo() { - todos_file="$(_get_todos_file)" - if [[ -f "$todos_file" ]]; then - data="$(cat "$todos_file")" - echo "$data" | \ - jq --arg id $argc_id '[.[] | select(.id != ($id | tonumber))]' \ - > "$todos_file" - echo "Successfully deleted todo id=$argc_id" - else - echo "Empty todo list" - fi -} - -# @cmd Display the current todo list in json format. -list_todos() { - todos_file="$(_get_todos_file)" - if [[ -f "$todos_file" ]]; then - cat "$todos_file" - else - echo '[]' - fi -} - -# @cmd Delete the entire todo list. -clear_todos() { - todos_file="$(_get_todos_file)" - if [[ -f "$todos_file" ]]; then - rm -rf "$todos_file" - fi - echo "Successfully deleted entry todo list" -} - -_argc_before() { - todos_file="$(_get_todos_file)" - mkdir -p "$(dirname "$todos_file")" -} - -_get_todos_file() { - echo "${LLM_BOT_CACHE_DIR:-/tmp}/todos.json" -} - -# See more details at https://github.com/sigoden/argc -eval "$(argc --argc-eval "$0" "$@")" diff --git a/scripts/run-agent.js b/scripts/run-agent.js new file mode 100755 index 0000000..69dd119 --- /dev/null +++ b/scripts/run-agent.js @@ -0,0 +1,107 @@ +#!/usr/bin/env node + +const path = require("path"); +const fs = require("fs"); +const os = require("os"); + +async function main() { + const [agentName, agentFunc, rawData] = parseArgv("run-agent.js"); + const agentData = parseRawData(rawData); + + const rootDir = path.resolve(__dirname, ".."); + setupEnv(rootDir, agentName); + + const agentToolsPath = path.resolve(rootDir, `agents/${agentName}/tools.js`); + await run(agentToolsPath, agentFunc, agentData); +} + +function parseArgv(thisFileName) { + let agentName = process.argv[1]; + let agentFunc = ""; + let agentData = null; + + if (agentName.endsWith(thisFileName)) { + agentName = process.argv[2]; + agentFunc = process.argv[3]; + agentData = process.argv[4]; + } else { + agentName = path.basename(agentName); + agentFunc = process.argv[2]; + agentData = process.argv[3]; + } + + if (agentName.endsWith(".js")) { + agentName = agentName.slice(0, -3); + } + + return [agentName, agentFunc, agentData]; +} + +function parseRawData(data) { + if (!data) { + throw new Error("No JSON data"); + } + try { + return JSON.parse(data); + } catch { + throw new Error("Invalid JSON data"); + } +} + +function setupEnv(rootDir, agentName) { + process.env["LLM_ROOT_DIR"] = rootDir; + loadEnv(path.resolve(rootDir, ".env")); + process.env["LLM_AGENT_NAME"] = agentName; + process.env["LLM_AGENT_ROOT_DIR"] = path.resolve(rootDir, "agents", agentName); + process.env["LLM_AGENT_CACHE_DIR"] = path.resolve(rootDir, "cache", agentName); +} + +function loadEnv(filePath) { + try { + const data = fs.readFileSync(filePath, "utf-8"); + const lines = data.split("\n"); + + lines.forEach((line) => { + if (line.trim().startsWith("#") || line.trim() === "") return; + + const [key, ...value] = line.split("="); + process.env[key.trim()] = value.join("=").trim(); + }); + } catch {} +} + +async function run(agentPath, agentFunc, agentData) { + let mod; + if (os.platform() === "win32") { + agentPath = `file://${agentPath}`; + } + try { + mod = await import(agentPath); + } catch { + throw new Error(`Unable to load agent tools at '${agentPath}'`); + } + if (!mod || !mod[agentFunc]) { + throw new Error(`Not module function '${agentFunc}' at '${agentPath}'`); + } + const value = await mod[agentFunc](agentData); + dumpValue(value); +} + +function dumpValue(value) { + if (value === null || value === undefined) { + return; + } + const type = typeof value; + if (type === "string" || type === "number" || type === "boolean") { + console.log(value); + } else if (type === "object") { + const proto = Object.prototype.toString.call(value); + if (proto === "[object Object]" || proto === "[object Array]") { + const valueStr = JSON.stringify(value, null, 2); + require("assert").deepStrictEqual(value, JSON.parse(valueStr)); + console.log(valueStr); + } + } +} + +main(); diff --git a/scripts/run-agent.py b/scripts/run-agent.py new file mode 100755 index 0000000..1239753 --- /dev/null +++ b/scripts/run-agent.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +import os +import json +import sys +import importlib.util + + +def main(): + (agent_name, agent_func, raw_data) = parse_argv("run-agent.py") + agent_data = parse_raw_data(raw_data) + + root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + setup_env(root_dir, agent_name) + + agent_tools_path = os.path.join(root_dir, f"agents/{agent_name}/tools.py") + run(agent_tools_path, agent_func, agent_data) + + +def parse_raw_data(data): + if not data: + raise ValueError("No JSON data") + + try: + return json.loads(data) + except Exception: + raise ValueError("Invalid JSON data") + + +def parse_argv(this_file_name): + argv = sys.argv[:] + [None] * max(0, 4 - len(sys.argv)) + + agent_name = argv[0] + agent_func = "" + agent_data = None + + if agent_name.endswith(this_file_name): + agent_name = sys.argv[1] + agent_func = sys.argv[2] + agent_data = sys.argv[3] + else: + agent_name = os.path.basename(agent_name) + agent_func = sys.argv[1] + agent_data = sys.argv[2] + + if agent_name.endswith(".py"): + agent_name = agent_name[:-3] + + return agent_name, agent_func, agent_data + + +def setup_env(root_dir, agent_name): + os.environ["LLM_ROOT_DIR"] = root_dir + load_env(os.path.join(root_dir, ".env")) + os.environ["LLM_AGENT_NAME"] = agent_name + os.environ["LLM_AGENT_ROOT_DIR"] = os.path.join(root_dir, "agents", agent_name) + os.environ["LLM_AGENT_CACHE_DIR"] = os.path.join(root_dir, "cache", agent_name) + + +def load_env(file_path): + try: + with open(file_path, "r") as f: + for line in f: + line = line.strip() + if line.startswith("#") or line == "": + continue + + key, *value = line.split("=") + os.environ[key.strip()] = "=".join(value).strip() + except FileNotFoundError: + pass + + +def run(agent_path, agent_func, agent_data): + try: + spec = importlib.util.spec_from_file_location( + os.path.basename(agent_path), agent_path + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + except: + raise Exception(f"Unable to load agent tools at '{agent_path}'") + + if not hasattr(mod, agent_func): + raise Exception(f"Not module function '{agent_func}' at '{agent_path}'") + + value = getattr(mod, agent_func)(**agent_data) + dump_value(value) + + +def dump_value(value): + if value is None: + return + + value_type = type(value).__name__ + if value_type in ("str", "int", "float", "bool"): + print(value) + elif value_type == "dict" or value_type == "list": + value_str = json.dumps(value, indent=2) + assert value == json.loads(value_str) + print(value_str) + + +if __name__ == "__main__": + main() diff --git a/scripts/run-agent.sh b/scripts/run-agent.sh new file mode 100755 index 0000000..0bc2ecd --- /dev/null +++ b/scripts/run-agent.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -e + +main() { + this_file_name=run-agent.sh + parse_argv "$@" + root_dir="$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd)" + setup_env + agent_tools_path="$root_dir/agents/$agent_name/tools.sh" + run +} + +parse_argv() { + if [[ "$0" == *"$this_file_name" ]]; then + agent_name="$1" + agent_func="$2" + agent_data="$3" + else + agent_name="$(basename "$0")" + agent_func="$1" + agent_data="$2" + fi + if [[ "$agent_name" == *.sh ]]; then + agent_name="${agent_name:0:$((${#agent_name}-3))}" + fi +} + +setup_env() { + export LLM_ROOT_DIR="$root_dir" + if [[ -f "$LLM_ROOT_DIR/.env" ]]; then + source "$LLM_ROOT_DIR/.env" + fi + export LLM_AGENT_NAME="$agent_name" + export LLM_AGENT_ROOT_DIR="$LLM_ROOT_DIR/agents/$agent_name" + export LLM_AGENT_CACHE_DIR="$LLM_ROOT_DIR/cache/$agent_name" +} + +run() { + if [[ -z "$agent_data" ]]; then + die "No JSON data" + fi + + _jq=jq + if [[ "$OS" == "Windows_NT" ]]; then + _jq="jq -b" + agent_tools_path="$(cygpath -w "$agent_tools_path")" + fi + + data="$( + echo "$agent_data" | \ + $_jq -r ' + to_entries | .[] | + (.key | split("_") | join("-")) as $key | + if .value | type == "array" then + .value | .[] | "--\($key)\n\(. | @json)" + elif .value | type == "boolean" then + if .value then "--\($key)" else "" end + else + "--\($key)\n\(.value | @json)" + end' + )" || { + die "Invalid JSON data" + } + while IFS= read -r line; do + if [[ "$line" == '--'* ]]; then + args+=("$line") + else + args+=("$(echo "$line" | $_jq -r '.')") + fi + done <<< "$data" + "$agent_tools_path" "$agent_func" "${args[@]}" +} + +die() { + echo "$*" >&2 + exit 1 +} + +main "$@" + diff --git a/scripts/run-bot.js b/scripts/run-bot.js deleted file mode 100755 index 7a5b70c..0000000 --- a/scripts/run-bot.js +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env node - -const path = require("path"); -const fs = require("fs"); -const os = require("os"); - -async function main() { - const [botName, botFunc, rawData] = parseArgv("run-bot.js"); - const botData = parseRawData(rawData); - - const rootDir = path.resolve(__dirname, ".."); - setupEnv(rootDir, botName); - - const botToolsPath = path.resolve(rootDir, `bots/${botName}/tools.js`); - await run(botToolsPath, botFunc, botData); -} - -function parseArgv(thisFileName) { - let botName = process.argv[1]; - let botFunc = ""; - let botData = null; - - if (botName.endsWith(thisFileName)) { - botName = process.argv[2]; - botFunc = process.argv[3]; - botData = process.argv[4]; - } else { - botName = path.basename(botName); - botFunc = process.argv[2]; - botData = process.argv[3]; - } - - if (botName.endsWith(".js")) { - botName = botName.slice(0, -3); - } - - return [botName, botFunc, botData]; -} - -function parseRawData(data) { - if (!data) { - throw new Error("No JSON data"); - } - try { - return JSON.parse(data); - } catch { - throw new Error("Invalid JSON data"); - } -} - -function setupEnv(rootDir, botName) { - process.env["LLM_ROOT_DIR"] = rootDir; - loadEnv(path.resolve(rootDir, ".env")); - process.env["LLM_BOT_NAME"] = botName; - process.env["LLM_BOT_ROOT_DIR"] = path.resolve(rootDir, "bots", botName); - process.env["LLM_BOT_CACHE_DIR"] = path.resolve(rootDir, "cache", botName); -} - -function loadEnv(filePath) { - try { - const data = fs.readFileSync(filePath, "utf-8"); - const lines = data.split("\n"); - - lines.forEach((line) => { - if (line.trim().startsWith("#") || line.trim() === "") return; - - const [key, ...value] = line.split("="); - process.env[key.trim()] = value.join("=").trim(); - }); - } catch {} -} - -async function run(botPath, botFunc, botData) { - let mod; - if (os.platform() === "win32") { - botPath = `file://${botPath}`; - } - try { - mod = await import(botPath); - } catch { - throw new Error(`Unable to load bot tools at '${botPath}'`); - } - if (!mod || !mod[botFunc]) { - throw new Error(`Not module function '${botFunc}' at '${botPath}'`); - } - const value = await mod[botFunc](botData); - dumpValue(value); -} - -function dumpValue(value) { - if (value === null || value === undefined) { - return; - } - const type = typeof value; - if (type === "string" || type === "number" || type === "boolean") { - console.log(value); - } else if (type === "object") { - const proto = Object.prototype.toString.call(value); - if (proto === "[object Object]" || proto === "[object Array]") { - const valueStr = JSON.stringify(value, null, 2); - require("assert").deepStrictEqual(value, JSON.parse(valueStr)); - console.log(valueStr); - } - } -} - -main(); diff --git a/scripts/run-bot.py b/scripts/run-bot.py deleted file mode 100755 index d7c7ae1..0000000 --- a/scripts/run-bot.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python - -import os -import json -import sys -import importlib.util - - -def main(): - (bot_name, bot_func, raw_data) = parse_argv("run-bot.py") - bot_data = parse_raw_data(raw_data) - - root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) - setup_env(root_dir, bot_name) - - bot_tools_path = os.path.join(root_dir, f"bots/{bot_name}/tools.py") - run(bot_tools_path, bot_func, bot_data) - - -def parse_raw_data(data): - if not data: - raise ValueError("No JSON data") - - try: - return json.loads(data) - except Exception: - raise ValueError("Invalid JSON data") - - -def parse_argv(this_file_name): - argv = sys.argv[:] + [None] * max(0, 4 - len(sys.argv)) - - bot_name = argv[0] - bot_func = "" - bot_data = None - - if bot_name.endswith(this_file_name): - bot_name = sys.argv[1] - bot_func = sys.argv[2] - bot_data = sys.argv[3] - else: - bot_name = os.path.basename(bot_name) - bot_func = sys.argv[1] - bot_data = sys.argv[2] - - if bot_name.endswith(".py"): - bot_name = bot_name[:-3] - - return bot_name, bot_func, bot_data - - -def setup_env(root_dir, bot_name): - os.environ["LLM_ROOT_DIR"] = root_dir - load_env(os.path.join(root_dir, ".env")) - os.environ["LLM_BOT_NAME"] = bot_name - os.environ["LLM_BOT_ROOT_DIR"] = os.path.join(root_dir, "bots", bot_name) - os.environ["LLM_BOT_CACHE_DIR"] = os.path.join(root_dir, "cache", bot_name) - - -def load_env(file_path): - try: - with open(file_path, "r") as f: - for line in f: - line = line.strip() - if line.startswith("#") or line == "": - continue - - key, *value = line.split("=") - os.environ[key.strip()] = "=".join(value).strip() - except FileNotFoundError: - pass - - -def run(bot_path, bot_func, bot_data): - try: - spec = importlib.util.spec_from_file_location( - os.path.basename(bot_path), bot_path - ) - mod = importlib.util.module_from_spec(spec) - spec.loader.exec_module(mod) - except: - raise Exception(f"Unable to load bot tools at '{bot_path}'") - - if not hasattr(mod, bot_func): - raise Exception(f"Not module function '{bot_func}' at '{bot_path}'") - - value = getattr(mod, bot_func)(**bot_data) - dump_value(value) - - -def dump_value(value): - if value is None: - return - - value_type = type(value).__name__ - if value_type in ("str", "int", "float", "bool"): - print(value) - elif value_type == "dict" or value_type == "list": - value_str = json.dumps(value, indent=2) - assert value == json.loads(value_str) - print(value_str) - - -if __name__ == "__main__": - main() diff --git a/scripts/run-bot.sh b/scripts/run-bot.sh deleted file mode 100755 index a709453..0000000 --- a/scripts/run-bot.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env bash -set -e - -main() { - this_file_name=run-bot.sh - parse_argv "$@" - root_dir="$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd)" - setup_env - bot_tools_path="$root_dir/bots/$bot_name/tools.sh" - run -} - -parse_argv() { - if [[ "$0" == *"$this_file_name" ]]; then - bot_name="$1" - bot_func="$2" - bot_data="$3" - else - bot_name="$(basename "$0")" - bot_func="$1" - bot_data="$2" - fi - if [[ "$bot_name" == *.sh ]]; then - bot_name="${bot_name:0:$((${#bot_name}-3))}" - fi -} - -setup_env() { - export LLM_ROOT_DIR="$root_dir" - if [[ -f "$LLM_ROOT_DIR/.env" ]]; then - source "$LLM_ROOT_DIR/.env" - fi - export LLM_BOT_NAME="$bot_name" - export LLM_BOT_ROOT_DIR="$LLM_ROOT_DIR/bots/$bot_name" - export LLM_BOT_CACHE_DIR="$LLM_ROOT_DIR/cache/$bot_name" -} - -run() { - if [[ -z "$bot_data" ]]; then - die "No JSON data" - fi - - _jq=jq - if [[ "$OS" == "Windows_NT" ]]; then - _jq="jq -b" - bot_tools_path="$(cygpath -w "$bot_tools_path")" - fi - - data="$( - echo "$bot_data" | \ - $_jq -r ' - to_entries | .[] | - (.key | split("_") | join("-")) as $key | - if .value | type == "array" then - .value | .[] | "--\($key)\n\(. | @json)" - elif .value | type == "boolean" then - if .value then "--\($key)" else "" end - else - "--\($key)\n\(.value | @json)" - end' - )" || { - die "Invalid JSON data" - } - while IFS= read -r line; do - if [[ "$line" == '--'* ]]; then - args+=("$line") - else - args+=("$(echo "$line" | $_jq -r '.')") - fi - done <<< "$data" - "$bot_tools_path" "$bot_func" "${args[@]}" -} - -die() { - echo "$*" >&2 - exit 1 -} - -main "$@" - -- cgit v1.2.3