├── .gitignore ├── README.md ├── app.json ├── chainsocket.py └── plugins ├── agent_basic.wasm ├── conversation-agent ├── .gitignore ├── Cargo.toml └── src │ └── lib.rs ├── conversation_agent.wasm ├── google-search ├── Cargo.toml └── src │ └── lib.rs ├── google_search.wasm ├── openai.wasm ├── openai ├── Cargo.toml └── src │ └── lib.rs ├── self-ask ├── Cargo.toml └── src │ └── lib.rs └── self_ask.wasm /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore compiled binaries and generated files 2 | *.o 3 | *.out 4 | *.exe 5 | 6 | # Ignore the secrets.json file 7 | secrets.json 8 | 9 | **/target 10 | *.lock 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Overview 3 | This proof of concept aims to demonstrate a versatile software platform inspired by LangChain, facilitating the creation of generative AI applications through portable plugins. These plugins, written in various languages, can run on different host platforms with support for cross-environment compatibility and security through the use of [WebAssembly](https://webassembly.org/) and [Extism](https://extism.org/). 4 | 5 | ## Setup 6 | Create a "secrets.json" file in the root directory with your [SerpApi](https://serpapi.com/) and [OpenAI](https://openai.com/) API keys 7 | 8 | ```json 9 | { 10 | "openai_apikey": "", 11 | "google_apikey": "" 12 | } 13 | ``` 14 | 15 | ## Run 16 | ``` 17 | python3 chainsocket.py 18 | ``` 19 | Start chatting with the bot! Type 'end' to exit the conversation 20 | 21 | ## Modify a Plug-in 22 | - cd into the plugin directory (e.g. cd plugins/self-ask) 23 | - modify the lib.rs as desired 24 | ``` 25 | cargo build --release --target wasm32-unknown-unknown 26 | ``` 27 | - copy the the .wasm in "./target/wasm32-unknown-unknown/release to the top level of the plugins directory 28 | 29 | ## Create a new Plug-in 30 | Follow the instructions at https://extism.org/docs/category/write-a-plug-in 31 | 32 | > Each plug-in must implement a "call" function 33 | 34 | 35 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "tools": [ 3 | { 4 | "name": "google_search", 5 | "description": "runs a google search given an input string", 6 | "plugin_name": "google_search.wasm" 7 | } 8 | ], 9 | "llms": [ 10 | { 11 | "name": "openai", 12 | "description": "calls chatGPT ", 13 | "plugin_name": "openai.wasm" 14 | } 15 | ], 16 | "agents": [ 17 | { 18 | "name": "self_ask_agent", 19 | "description": "self ask with search agent", 20 | "plugin_name": "self_ask.wasm", 21 | "prompt": "", 22 | "tools": ["google_search"], 23 | "llms": ["openai"] 24 | }, 25 | { 26 | "name": "conversational_agent", 27 | "description": "A conversational agent", 28 | "plugin_name": "conversation_agent.wasm", 29 | "prompt": "You speak like a pirate when you reply. Your answers are very short and to the point", 30 | "tools": [], 31 | "llms": ["openai"] 32 | } 33 | ], 34 | "entry": "self_ask_agent" 35 | } -------------------------------------------------------------------------------- /chainsocket.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pathlib 3 | import hashlib 4 | import json 5 | import os 6 | from dataclasses import dataclass 7 | from dataclasses_json import dataclass_json 8 | 9 | import extism 10 | from extism import Function, host_fn, ValType, Plugin, set_log_file 11 | 12 | @dataclass_json 13 | @dataclass 14 | class PluginMetadata: 15 | name: str 16 | version: str 17 | entry: str 18 | description: str 19 | 20 | class PluginManifest: 21 | path: str 22 | module_data: bytes 23 | metadata: PluginMetadata 24 | config: dict 25 | 26 | def __init__(self, filename: str, config) -> None: 27 | abs_path = os.path.dirname(__file__) 28 | rel_path = "plugins" 29 | self.path = os.path.join(abs_path, rel_path, filename) 30 | self.module_data = open(self.path, "rb").read() 31 | 32 | hash = hashlib.sha256(self.module_data).hexdigest() 33 | self.config = { "wasm": [{"data": self.module_data, "hash": hash}], "memory": {"max": 5}, "allowed_hosts": ["api.openai.com", "serpapi.com"] } 34 | 35 | if(config): 36 | self.config["config"] = config 37 | 38 | class ChainSocketPlugin: 39 | manifest: PluginManifest 40 | 41 | def __init__(self, plugin_name: str, hostfuncs, config) -> None: 42 | self.manifest = PluginManifest(plugin_name, config) 43 | self.ctx = extism.Context() 44 | 45 | self.plugin = self.ctx.plugin(self.manifest.config, wasi=True, functions=hostfuncs) 46 | 47 | def call(self, func: str, data) -> any: 48 | return self.plugin.call(func, data) 49 | 50 | def execute(self, input: str): 51 | json_data = json.dumps(input) 52 | response = self.call("call", json_data) 53 | return response 54 | 55 | def free(self): 56 | self.ctx.free() 57 | 58 | @dataclass_json 59 | @dataclass 60 | class Tool(ChainSocketPlugin): 61 | name: str 62 | description: str 63 | plugin_name: str 64 | 65 | def load(self, hostfuncs, secrets): 66 | config = { 67 | 'google_apikey': secrets.google_apikey, 68 | 'name': self.name 69 | } 70 | super().__init__(self.plugin_name, hostfuncs, config) 71 | 72 | @dataclass_json 73 | @dataclass 74 | class Llm(ChainSocketPlugin): 75 | name: str 76 | description: str 77 | plugin_name: str 78 | 79 | def load(self, hostfuncs, secrets): 80 | config = { 81 | 'openai_apikey': secrets.openai_apikey, 82 | 'name': self.name 83 | } 84 | 85 | super().__init__(self.plugin_name, hostfuncs, config) 86 | 87 | @dataclass_json 88 | @dataclass 89 | class Secrets: 90 | openai_apikey: str 91 | google_apikey: str 92 | 93 | @dataclass_json 94 | @dataclass 95 | class Agent(ChainSocketPlugin): 96 | name: str 97 | description: str 98 | plugin_name: str 99 | prompt: str 100 | tools: list[str] 101 | llms: list[str] 102 | 103 | def load(self, hostfuncs, secrets): 104 | # set the plugin configuration with the contents read from the app.json 105 | config = { 106 | 'prompt': self.prompt, 107 | 'openai_apikey': secrets.openai_apikey, 108 | 'llm_name': self.llms[0], 109 | 'name': self.name 110 | } 111 | super().__init__(self.plugin_name, hostfuncs, config) 112 | 113 | @dataclass_json 114 | @dataclass 115 | class App: 116 | tools: list[Tool] 117 | agents: list[Agent] 118 | llms: list[Llm] 119 | entry: str 120 | 121 | def get_plugin(self, type, name): 122 | entry = [ plugin for plugin in self.registry if isinstance(plugin, type) and plugin.name == name ] 123 | if not entry: 124 | return None 125 | else: 126 | return entry[0] 127 | 128 | def call_plugin(self, plugin_type, req): 129 | plugin = app.get_plugin(plugin_type, req["name"]) 130 | 131 | if plugin is not None: 132 | rep = plugin.execute(req) 133 | data = { 'output': rep.decode('utf-8'), } 134 | else: 135 | data = { 'output': "None", } 136 | 137 | return json.dumps(data) 138 | 139 | def load(self, hostfuncs): 140 | 141 | with open("secrets.json", "r") as f: 142 | secrets = Secrets.schema().loads(f.read()) 143 | 144 | self.registry = [] 145 | 146 | for tool in self.tools: 147 | try: 148 | tool.load([], secrets) 149 | self.registry.append(tool) 150 | except Exception as e: 151 | print("unable to locate/load plugin: {} for tool: {} exception: {}".format(tool.plugin_name, tool.name, e)) 152 | 153 | for agent in self.agents: 154 | try: 155 | agent.load(hostfuncs, secrets) 156 | self.registry.append(agent) 157 | except Exception as e: 158 | print("unable to locate/load plugin: {} for agent: {} exception: {}".format(agent.plugin_name, agent.name, e)) 159 | 160 | for llm in self.llms: 161 | try: 162 | llm.load(hostfuncs, secrets) 163 | self.registry.append(llm) 164 | except Exception as e: 165 | print("unable to locate/load plugin: {} for agent: {} exception: {}".format(llm.plugin_name, llm.name, e)) 166 | 167 | # define the Extism Host Functions. These will be provided as exports to the plugins 168 | @host_fn 169 | def call_agent(plugin, input_, output, a_string): 170 | req = json.loads(plugin.input_string(input_[0])) 171 | rep = app.call_plugin(Agent, req) 172 | plugin.return_string(output[0], rep) 173 | 174 | @host_fn 175 | def call_tool(plugin, input_, output, a_string): 176 | req = json.loads(plugin.input_string(input_[0])) 177 | rep = app.call_plugin(Tool, req) 178 | plugin.return_string(output[0], rep) 179 | 180 | @host_fn 181 | def call_llm(plugin, input_, output, a_string): 182 | req = json.loads(plugin.input_string(input_[0])) 183 | rep = app.call_plugin(Llm, req) 184 | plugin.return_string(output[0], rep) 185 | 186 | with open("app.json", "r") as f: 187 | app = App.schema().loads(f.read()) 188 | 189 | hostfuncs = [ 190 | Function( 191 | "call_tool", 192 | [ValType.I64], 193 | [ValType.I64], 194 | call_tool, 195 | None 196 | ), 197 | Function( 198 | "call_llm", 199 | [ValType.I64], 200 | [ValType.I64], 201 | call_llm, 202 | None 203 | ), 204 | Function( 205 | "call_agent", 206 | [ValType.I64], 207 | [ValType.I64], 208 | call_agent, 209 | None 210 | ) 211 | ] 212 | 213 | app.load(hostfuncs) 214 | 215 | def main(args): 216 | 217 | if app.entry is None: 218 | print("no entry agent found") 219 | sys.exit(0) 220 | 221 | set_log_file('chain.out', level='debug') 222 | 223 | while True: 224 | print("You > ", end='') 225 | human_input = input() 226 | if human_input == 'end': 227 | break 228 | 229 | agent = app.get_plugin(Agent, app.entry) 230 | data = { 231 | 'name': agent.name, 232 | 'input': human_input 233 | } 234 | 235 | rep = agent.execute(data) 236 | print("Agent: ", rep) 237 | 238 | if __name__ == "__main__": 239 | main(sys.argv) -------------------------------------------------------------------------------- /plugins/agent_basic.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylibso/chainsocket/d13ba9a4f1dc3ae24ba71af43cc2aa151e48a573/plugins/agent_basic.wasm -------------------------------------------------------------------------------- /plugins/conversation-agent/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /plugins/conversation-agent/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "conversation-agent" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate_type = ["cdylib"] 10 | 11 | [dependencies] 12 | extism-pdk = "0.3.3" 13 | serde = "1.0.171" 14 | serde_json = "1.0.102" 15 | -------------------------------------------------------------------------------- /plugins/conversation-agent/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use std::{str::from_utf8, fmt}; 4 | use extism_pdk::*; 5 | use serde::{Serialize, Deserialize}; 6 | use serde_json::json; 7 | 8 | #[derive(Serialize)] 9 | struct PluginMetadata { 10 | name: String, 11 | version: String, 12 | entry: String, 13 | description: String, 14 | } 15 | 16 | #[derive(Serialize, Deserialize, Debug, Clone)] 17 | struct LLMReq { 18 | name: String, 19 | systemprompt: String, 20 | inputprompt: String, 21 | stop: Vec, 22 | } 23 | 24 | #[derive(Serialize, Deserialize, Debug, Clone)] 25 | struct AgentReq { 26 | name: String, 27 | input: String, 28 | } 29 | 30 | #[derive(Serialize, Deserialize, Debug, Clone)] 31 | struct ActionRep { 32 | output: String, 33 | } 34 | 35 | #[host_fn] 36 | extern "ExtismHost" { 37 | fn call_llm(req: Json) -> Json; 38 | } 39 | 40 | #[host_fn] 41 | extern "ExtismHost" { 42 | fn call_agent(req: Json) -> Json; 43 | } 44 | 45 | #[plugin_fn] 46 | pub unsafe fn call<'a>(Json(mut input): Json) -> FnResult { 47 | 48 | let configprompt = config::get("prompt").expect("Could not find config key 'prompt'"); 49 | let llm = config::get("llm_name").expect("Could not find config key 'llm_name'"); 50 | //let delegate = config::get("delegate").expect("Could not find config key 'delegate'"); 51 | let name = config::get("name").expect("Could not find config key 'name'"); 52 | 53 | // input.name = delegate.clone(); 54 | // info!("AGENT {}: Calling Delegate: {:#?}", name, input); 55 | // let Json(rep) = unsafe { call_agent(Json(input.clone()))? }; 56 | // info!("AGENT {}: Response from Delegate: {:#?}", name, rep); 57 | 58 | let history = match var::get("memory") { 59 | Ok(Some(bytes)) => String::from_utf8(bytes)?, 60 | _ => String::from("\nHere is the history of the chat with the human you are assisting\n"), 61 | }; 62 | 63 | let systemprompt = format!("{} {}.", configprompt.clone(), history.clone()); 64 | 65 | let req = LLMReq { 66 | name: llm.clone(), 67 | systemprompt: systemprompt, 68 | inputprompt: input.input.clone(), 69 | stop: vec![], 70 | }; 71 | 72 | // call the configured LLM plugin via our custom Extism Host Function 73 | info!("AGENT {}: Calling LLM: {:#?}", name, req); 74 | let Json(rep) = unsafe { call_llm(Json(req))? }; 75 | info!("AGENT {}: Response from LLM: {:#?}", name, rep); 76 | 77 | let mut systemprompt = history.clone(); 78 | systemprompt.push_str("Human: "); 79 | systemprompt.push_str(&input.input); 80 | systemprompt.push_str("\n"); 81 | systemprompt.push_str("Assistant: "); 82 | systemprompt.push_str(&rep.output); 83 | set_var!("memory", "{}", systemprompt.as_str())?; 84 | 85 | Ok(rep.output.clone()) 86 | } 87 | -------------------------------------------------------------------------------- /plugins/conversation_agent.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylibso/chainsocket/d13ba9a4f1dc3ae24ba71af43cc2aa151e48a573/plugins/conversation_agent.wasm -------------------------------------------------------------------------------- /plugins/google-search/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "google_search" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate_type = ["cdylib"] 10 | 11 | [dependencies] 12 | extism-pdk = "0.3.3" 13 | serde = "1.0.171" 14 | serde_json = "1.0.102" 15 | -------------------------------------------------------------------------------- /plugins/google-search/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use std::str::from_utf8; 4 | use extism_pdk::*; 5 | use serde::{Serialize, Deserialize}; 6 | use serde_json::json; 7 | use serde_json::Value; 8 | 9 | 10 | #[derive(Serialize)] 11 | struct PluginMetadata { 12 | name: String, 13 | version: String, 14 | entry: String, 15 | description: String, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, Debug, Clone)] 19 | struct ActionRep { 20 | output: String, 21 | } 22 | 23 | #[derive(Serialize, Deserialize, Debug, Clone)] 24 | struct ToolReq { 25 | name: String, 26 | input: String, 27 | } 28 | 29 | #[plugin_fn] 30 | pub unsafe fn call<'a>(Json(input): Json) -> FnResult { 31 | info!("plugin (google_search) function called"); 32 | let api_key = config::get("google_apikey").expect("Could not find config key 'google_apikey'"); 33 | let name = config::get("name").expect("Could not find config key 'name'"); 34 | 35 | let req = HttpRequest::new("https://serpapi.com/search") 36 | .with_header("Content-Type", "application/json") 37 | .with_method("GET"); 38 | 39 | info!("TOOL {}: Request recevied: {:#?}", name, input.input); 40 | 41 | let req_body = json!({ 42 | "engine": "google", 43 | "q": input.input, 44 | "google_domain": "google.com", 45 | "gl": "us", 46 | "hl": "en", 47 | "api_key": api_key, 48 | }); 49 | 50 | info!("TOOL {}: Making request to Google: {:#?}", name, req_body); 51 | 52 | let res = http::request::(&req, Some(req_body.to_string()))?; 53 | let body = res.body(); 54 | let body = from_utf8(&body)?; 55 | 56 | let res: Value = serde_json::from_str(body)?; 57 | 58 | info!("TOOL {}: Received results: {}", name, res); 59 | 60 | let toret: Option<&str>; 61 | 62 | if res.get("answer_box").and_then(|box_val| box_val.get("answer")).is_some() { 63 | toret = res["answer_box"]["answer"].as_str(); 64 | } else if res.get("answer_box").and_then(|box_val| box_val.get("snippet")).is_some() { 65 | toret = res["answer_box"]["snippet"].as_str(); 66 | } else if res.get("answer_box") 67 | .and_then(|box_val| box_val.get("snippet_highlighted_words")) 68 | .and_then(|highlighted_words| highlighted_words.get(0)) 69 | .is_some() 70 | { 71 | toret = res["answer_box"]["snippet_highlighted_words"][0].as_str(); 72 | } else if let Some(organic_results) = res.get("organic_results") { 73 | if let Some(first_result) = organic_results.get(0) { 74 | toret = first_result.get("snippet").and_then(|snippet| snippet.as_str()); 75 | } else { 76 | toret = None; 77 | } 78 | } else { 79 | toret = None; 80 | } 81 | 82 | Ok(toret.unwrap().to_string()) 83 | 84 | } 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /plugins/google_search.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylibso/chainsocket/d13ba9a4f1dc3ae24ba71af43cc2aa151e48a573/plugins/google_search.wasm -------------------------------------------------------------------------------- /plugins/openai.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylibso/chainsocket/d13ba9a4f1dc3ae24ba71af43cc2aa151e48a573/plugins/openai.wasm -------------------------------------------------------------------------------- /plugins/openai/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openai" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate_type = ["cdylib"] 10 | 11 | [dependencies] 12 | extism-pdk = "0.3.3" 13 | serde = "1.0.171" 14 | serde_json = "1.0.102" 15 | -------------------------------------------------------------------------------- /plugins/openai/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use std::str::from_utf8; 4 | use extism_pdk::*; 5 | use serde::{Serialize, Deserialize}; 6 | use serde_json::json; 7 | 8 | 9 | #[derive(Deserialize)] 10 | struct ChatMessage { 11 | content: String, 12 | } 13 | 14 | #[derive(Deserialize)] 15 | struct ChatChoice { 16 | message: ChatMessage, 17 | } 18 | 19 | #[derive(Deserialize)] 20 | struct ChatResult { 21 | choices: Vec, 22 | } 23 | 24 | #[derive(Deserialize, Debug, Clone)] 25 | struct LLMReq { 26 | name: String, 27 | systemprompt: String, 28 | inputprompt: String, 29 | stop: Vec, 30 | } 31 | 32 | #[plugin_fn] 33 | pub unsafe fn call<'a>(Json(input): Json) -> FnResult { 34 | 35 | let api_key = config::get("openai_apikey").expect("Could not find config key 'openai_apikey'"); 36 | let name = config::get("name").expect("Could not find config key 'name'"); 37 | 38 | info!("LLM {}: Received Request {:#?}", name, input); 39 | 40 | let req = HttpRequest::new("https://api.openai.com/v1/chat/completions") 41 | .with_header("Authorization", format!("Bearer {}", api_key)) 42 | .with_header("Content-Type", "application/json") 43 | .with_method("POST"); 44 | 45 | let req_body = json!({ 46 | "model": "gpt-3.5-turbo", 47 | "temperature": 0, 48 | "stop": input.stop, 49 | "messages": [ 50 | { 51 | "role": "system", 52 | "content": input.systemprompt 53 | 54 | }, 55 | { 56 | "role": "user", 57 | "content": input.inputprompt, 58 | } 59 | ], 60 | }); 61 | 62 | info!("LLM {}: Making Call to OpenAI {}", name, req_body); 63 | 64 | let res = http::request::(&req, Some(req_body.to_string()))?; 65 | let body = res.body(); 66 | let body = from_utf8(&body)?; 67 | let body: ChatResult = serde_json::from_str(body)?; 68 | 69 | info!("LLM {}: Received Response from OpenAI {:#?}", name, body.choices[0].message.content); 70 | 71 | Ok(body.choices[0].message.content.clone()) 72 | 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /plugins/self-ask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "self-ask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | crate_type = ["cdylib"] 10 | 11 | [dependencies] 12 | extism-pdk = "0.3.3" 13 | serde = "1.0.171" 14 | serde_json = "1.0.102" 15 | -------------------------------------------------------------------------------- /plugins/self-ask/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use std::str::from_utf8; 4 | use extism_pdk::*; 5 | use serde::{Serialize, Deserialize}; 6 | use serde_json::json; 7 | use serde_json::Value; 8 | 9 | 10 | #[derive(Serialize, Deserialize, Debug, Clone)] 11 | struct LLMReq { 12 | name: String, 13 | systemprompt: String, 14 | inputprompt: String, 15 | stop: Vec, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, Debug, Clone)] 19 | struct ToolReq { 20 | name: String, 21 | input: String, 22 | } 23 | 24 | #[derive(Serialize, Deserialize, Debug, Clone)] 25 | struct AgentReq { 26 | name: String, 27 | input: String, 28 | } 29 | 30 | #[derive(Serialize, Deserialize, Debug, Clone)] 31 | struct ActionRep { 32 | output: String, 33 | } 34 | 35 | #[host_fn] 36 | extern "ExtismHost" { 37 | fn call_tool(req: Json) -> Json; 38 | } 39 | 40 | #[host_fn] 41 | extern "ExtismHost" { 42 | fn call_llm(req: Json) -> Json; 43 | } 44 | 45 | fn execute_tool(query: &str) -> FnResult { 46 | let req = ToolReq { 47 | name: "google_search".to_string(), 48 | input: query.to_string(), 49 | }; 50 | 51 | let Json(rep) = unsafe { call_tool(Json(req))? }; 52 | info!("TOOL RESULTS: {:#?}", rep); 53 | 54 | Ok(rep.output) 55 | 56 | } 57 | 58 | #[plugin_fn] 59 | pub unsafe fn call<'a>(Json(input): Json) -> FnResult { 60 | let llm_name = config::get("llm_name").expect("Could not find config key 'llm_name'"); 61 | let name = config::get("name").expect("Could not find config key 'name'"); 62 | 63 | let intermediate = "\nIntermediate answer:"; 64 | let followup = "Follow up:"; 65 | let finalans = "So the final answer is:"; 66 | 67 | let mut sysprompt = get_prompt()[0].to_string(); 68 | 69 | let mut query = format!("Question: {}\n{}", input.input, "Are follow up questions needed here:".to_string()); 70 | 71 | // build up request to the configured LLM 72 | let mut req = LLMReq { 73 | name: llm_name.clone(), 74 | systemprompt: sysprompt.clone(), 75 | inputprompt: query.clone(), 76 | stop: vec![format!("{}", intermediate)], 77 | }; 78 | 79 | // call the llm 80 | info!("AGENT {}: Calling LLM : {:#?}", name, req); 81 | let Json(mut rep) = unsafe { call_llm(Json(req.clone()))? }; 82 | info!("AGENT {}: Response from LLM : {:#?}", name, rep); 83 | 84 | req.systemprompt.push_str(&format!("{}", &query)); 85 | let mut ret_text = rep.output; 86 | 87 | while get_last_line(&ret_text).contains(followup) { 88 | 89 | req.systemprompt.push_str(&format!("{}", &ret_text)); 90 | 91 | let extracted_question = extract_question(&ret_text); 92 | info!("AGENT {}: Calling Tool with extracted question : {}", name, extracted_question); 93 | let external_answer = execute_tool(&extracted_question); 94 | info!("AGENT {}: Response from Tool : {:#?}", name, external_answer); 95 | 96 | if let Ok(external_answer) = external_answer { 97 | info!("AGENT {}: Received Answer from Tool", name); 98 | req.inputprompt = format!("{} {}.", intermediate, &external_answer); 99 | req.stop = vec![intermediate.to_string()]; 100 | info!("AGENT {}: Calling LLM : {:#?}", name, req); 101 | Json(rep) = unsafe { call_llm(Json(req.clone()))? }; 102 | info!("AGENT {}: Response from LLM : {:#?}", name, rep); 103 | ret_text = rep.output; 104 | } else { 105 | // We only get here in the very rare case that Google returns no answer. 106 | info!("AGENT {}: Received NO Answer from Tool", name); 107 | req.systemprompt.push_str(&format!("{}", intermediate)); 108 | req.stop = vec![format!("\n{}", followup), finalans.to_string()]; 109 | info!("AGENT {}: Calling LLM : {:#?}", name, req); 110 | let Json(gpt_answer) = unsafe { call_llm(Json(req.clone()))? }; 111 | info!("AGENT {}: Response from LLM : {:#?}", name, gpt_answer); 112 | req.systemprompt.push_str(&format!("{:#?}", gpt_answer)); 113 | } 114 | } 115 | 116 | if !ret_text.contains(finalans) { 117 | info!("AGENT {}: Couldn't conclude a final answer", name); 118 | req.systemprompt.push_str(&format!("{}", finalans)); 119 | req.stop = vec!["\n".to_string()]; 120 | info!("AGENT {}: Calling LLM : {:#?}", name, req); 121 | Json(rep) = unsafe { call_llm(Json(req.clone()))? }; 122 | info!("AGENT {}: Response from LLM : {:#?}", name, rep); 123 | ret_text = rep.output; 124 | } 125 | 126 | let clean = extract_answer(&ret_text); 127 | Ok(clean.to_string()) 128 | } 129 | 130 | fn extract_answer(generated: &str) -> String { 131 | let last_line = if !generated.contains('\n') { 132 | generated.to_string() 133 | } else { 134 | generated.lines().last().unwrap().to_string() 135 | }; 136 | 137 | let after_colon = if !last_line.contains(':') { 138 | last_line.to_string() 139 | } else { 140 | last_line.split(':').last().unwrap().to_string() 141 | }; 142 | 143 | let after_colon = if after_colon.starts_with(' ') { 144 | after_colon[1..].to_string() 145 | } else { 146 | after_colon 147 | }; 148 | 149 | let after_colon = if after_colon.ends_with('.') { 150 | after_colon[..after_colon.len() - 1].to_string() 151 | } else { 152 | after_colon 153 | }; 154 | 155 | after_colon 156 | } 157 | 158 | fn extract_question(generated: &str) -> String { 159 | let last_line = if !generated.contains('\n') { 160 | generated.to_string() 161 | } else { 162 | generated.lines().last().unwrap().to_string() 163 | }; 164 | 165 | assert!(last_line.contains("Follow up:"), "we probably should never get here...{}", generated); 166 | 167 | let after_colon = if !last_line.contains(':') { 168 | last_line.to_string() 169 | } else { 170 | last_line.split(':').last().unwrap().to_string() 171 | }; 172 | 173 | let after_colon = if after_colon.starts_with(' ') { 174 | after_colon[1..].to_string() 175 | } else { 176 | after_colon 177 | }; 178 | 179 | assert_eq!(after_colon.chars().last(), Some('?'), "we probably should never get here...{}", generated); 180 | 181 | after_colon 182 | } 183 | 184 | fn get_last_line(generated: &str) -> String { 185 | if !generated.contains('\n') { 186 | generated.to_string() 187 | } else { 188 | generated.lines().last().unwrap().to_string() 189 | } 190 | } 191 | 192 | fn get_prompt() -> Vec<&'static str> { 193 | let prompt: Vec<&str> = vec![ 194 | "Question: Who lived longer, Muhammad Ali or Alan Turing?\n 195 | Are follow up questions needed here: Yes. 196 | Follow up: How old was Muhammad Ali when he died? 197 | Intermediate answer: Muhammad Ali was 74 years old when he died. 198 | Follow up: How old was Alan Turing when he died? 199 | Intermediate answer: Alan Turing was 41 years old when he died. 200 | So the final answer is: Muhammad Ali\n 201 | Question: When was the founder of craigslist born?\n 202 | Are follow up questions needed here: Yes. 203 | Follow up: Who was the founder of craigslist? 204 | Intermediate answer: Craigslist was founded by Craig Newmark. 205 | Follow up: When was Craig Newmark born? 206 | Intermediate answer: Craig Newmark was born on December 6, 1952. 207 | So the final answer is: December 6, 1952\n 208 | Question: Who was the maternal grandfather of George Washington?\n 209 | Are follow up questions needed here: Yes. 210 | Follow up: Who was the mother of George Washington? 211 | Intermediate answer: The mother of George Washington was Mary Ball Washington. 212 | Follow up: Who was the father of Mary Ball Washington? 213 | Intermediate answer: The father of Mary Ball Washington was Joseph Ball. 214 | So the final answer is: Joseph Ball\n 215 | Question: Are both the directors of Jaws and Casino Royale from the same country?\n 216 | Are follow up questions needed here: Yes. 217 | Follow up: Who is the director of Jaws? 218 | Intermediate Answer: The director of Jaws is Steven Spielberg. 219 | Follow up: Where is Steven Spielberg from? 220 | Intermediate Answer: The United States. 221 | Follow up: Who is the director of Casino Royale? 222 | Intermediate Answer: The director of Casino Royale is Martin Campbell. 223 | Follow up: Where is Martin Campbell from? 224 | Intermediate Answer: New Zealand. 225 | So the final answer is: No\n" 226 | ]; 227 | return prompt 228 | } 229 | 230 | -------------------------------------------------------------------------------- /plugins/self_ask.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dylibso/chainsocket/d13ba9a4f1dc3ae24ba71af43cc2aa151e48a573/plugins/self_ask.wasm --------------------------------------------------------------------------------