Make AI do OS operations
To enable business processes, we'll often need OS operations. To enable this, we can knock up a tool, which can do that. There are serious security implications. You should probably do this sort of thing in a sandbox.
Here's a simple example of an OS tool. Note how simple this is to create.
import smithy4s.*
import smithy4s.deriving.{*, given}
import cats.effect.IO
import cats.effect.std.Console
@hints(smithy.api.Documentation("Local file and os operations"))
/** Local file and os operations
*/
trait OsTool derives API:
/** Creates a temporary directory on the local file system.
*/
def makeTempDir(dirPrefix: String): IO[String] =
IO.println("Creating a temporary directory") >>
IO.blocking {
val outDir = os.temp.dir(deleteOnExit = false, prefix = dirPrefix).toString
outDir.toString
}
def createOrOverwriteFileInDir(dir: String, fileName: String, contents: Option[String]): IO[String] =
IO.println(s"Creating a file in $dir") >>
IO.blocking {
val filePath = os.Path(dir) / fileName
os.write.over(filePath, contents.getOrElse(""))
filePath.toString
}
def askForHelp(question: String): IO[String] =
for
_ <- Console[IO].println(s"I need guidance with: $question")
n <- Console[IO].readLine
yield n
end OsTool
The below mechanism, demonstrates how one may supply that tool to the AI, and dispatch an arbirary function call.
import io.github.quafadas.dairect.*
import cats.effect.IOApp
import cats.effect.IO
import scala.annotation.experimental
import smithy4s.Document
import smithy4s.kinds.PolyFunction
import smithy4s.deriving.{*, given}
import scala.concurrent.duration.*
import cats.effect.unsafe.implicits.global
import scala.concurrent.Future
import io.github.quafadas.dairect.ChatGpt.AiMessage
import smithy4s.json.Json
import smithy4s.Blob
import fs2.io.file.*
import cats.effect.ExitCode
import org.http4s.ember.client.EmberClientBuilder
import ciris.*
val osImpl = new OsTool() {}
// osImpl: OsTool = repl.MdocSession$MdocApp$$anon$1@19062630
val logFile = fs2.io.file.Path("easychat.txt")
// logFile: Path = easychat.txt
val chatGpt = ChatGpt.defaultAuthLogToFile(logFile).allocated.map(_._1).Ø
// chatGpt: ChatGpt = io.github.quafadas.dairect.ChatGpt$proxy$1@184ada50
val osTools = API[OsTool].liftService(osImpl)
// osTools: Free[<none>] = LiftedAlgebra(
// alg = repl.MdocSession$MdocApp$$anon$1@19062630,
// fk = smithy4s.kinds.PolyFunction5$$anon$11@75bc2c54
// )
val schema = ioToolGen.toJsonSchema(osTools)
// schema: Document = DArray(
// value = ArraySeq(
// DObject(
// value = Map(
// "type" -> DString(value = "function"),
// "function" -> DObject(
// value = Map(
// "name" -> DString(value = "askForHelp"),
// "description" -> DString(value = "askForHelp"),
// "parameters" -> DObject(
// value = Map(
// "type" -> DString(value = "object"),
// "required" -> DArray(value = ArraySeq(DString(value = "question"))),
// "properties" -> DObject(
// value = Map(
// "question" -> DObject(value = Map("type" -> DString(value = "string")))
// )
// )
// )
// )
// )
// )
// )
// ),
// DObject(
// value = Map(
// "type" -> DString(value = "function"),
// "function" -> DObject(
// value = Map(
// "name" -> DString(value = "readTextFile"),
// "description" -> DString(value = "readTextFile"),
// "parameters" -> DObject(
// value = Map(
// "type" -> DString(value = "object"),
// "required" -> DArray(value = ArraySeq(DString(value = "filePath"))),
// "properties" -> DObject(
// value = Map(
// "filePath" -> DObject(value = Map("type" -> DString(value = "string")))
// )
// )
// )
// )
// )
// )
// )
// ...
val osDispatch = ioToolGen.openAiSmithyFunctionDispatch(osTools)
// osDispatch: Function1[FunctionCall, IO[Document]] = io.github.quafadas.dairect.SmithyOpenAIUtil$$Lambda$5450/0x00000008019fefe8@7d610e52
val resp = chatGpt
.chat(
List(AiMessage.system("You are a helpful assistant"), AiMessage.user("Create a temporary directory")),
tools = schema.some
)
.Ø
// resp: ChatResponse = ChatResponse(
// id = "chatcmpl-A1Zrj881u9xhQkxL7tq5ov9vChhdV",
// created = 1724939559,
// model = "gpt-4o-mini-2024-07-18",
// choices = List(
// AiChoice(
// message = AiAnswer(
// role = "assistant",
// content = None,
// tool_calls = Some(
// value = List(
// ToolCall(
// id = "call_VG5MHpXrZaGMo61ASSbXKVEw",
// type = "function",
// function = FunctionCall(
// name = "makeTempDir",
// description = None,
// arguments = Some(value = "{\"dirPrefix\":\"temp\"}")
// )
// )
// )
// )
// ),
// finish_reason = Some(value = "tool_calls")
// )
// ),
// usage = AiTokenUsage(
// completion_tokens = 16,
// prompt_tokens = 126,
// total_tokens = 142
// )
// )
val toolCall = resp.choices.head
// toolCall: AiChoice = AiChoice(
// message = AiAnswer(
// role = "assistant",
// content = None,
// tool_calls = Some(
// value = List(
// ToolCall(
// id = "call_VG5MHpXrZaGMo61ASSbXKVEw",
// type = "function",
// function = FunctionCall(
// name = "makeTempDir",
// description = None,
// arguments = Some(value = "{\"dirPrefix\":\"temp\"}")
// )
// )
// )
// )
// ),
// finish_reason = Some(value = "tool_calls")
// )
val fctCall = toolCall.message.tool_calls.get.head
// fctCall: ToolCall = ToolCall(
// id = "call_VG5MHpXrZaGMo61ASSbXKVEw",
// type = "function",
// function = FunctionCall(
// name = "makeTempDir",
// description = None,
// arguments = Some(value = "{\"dirPrefix\":\"temp\"}")
// )
// )
val out = osDispatch(fctCall.function).Ø
// out: Document = DString(value = "/tmp/temp5996774433281101439")
val tooloutcome = AiMessage.tool(tool_call_id = fctCall.id, content = Json.writeDocumentAsPrettyString(out))
// tooloutcome: AiMessage = AiMessage(
// role = "tool",
// content = Some(value = "\"/tmp/temp5996774433281101439\""),
// tool_calls = None,
// tool_call_id = Some(value = "call_VG5MHpXrZaGMo61ASSbXKVEw"),
// name = None
// )
chatGpt
.chat(
List(
AiMessage.system("You are a helpful assistant"),
AiMessage.user("Create a temporary directory"),
toolCall.toMessage.head,
tooloutcome
),
tools = schema.some
)
.Ø
// res0: ChatResponse = ChatResponse(
// id = "chatcmpl-A1ZrkGCySjudnH9rjQa5SrxNroe0X",
// created = 1724939560,
// model = "gpt-4o-mini-2024-07-18",
// choices = List(
// AiChoice(
// message = AiAnswer(
// role = "assistant",
// content = Some(
// value = "A temporary directory has been created at the path: `/tmp/temp5996774433281101439`."
// ),
// tool_calls = None
// ),
// finish_reason = Some(value = "stop")
// )
// ),
// usage = AiTokenUsage(
// completion_tokens = 22,
// prompt_tokens = 162,
// total_tokens = 184
// )
// )
In this article