Skip to content

Commit

Permalink
thoughts
Browse files Browse the repository at this point in the history
  • Loading branch information
fwbrasil committed Dec 31, 2023
1 parent ac94fb1 commit 75b171c
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 267 deletions.
40 changes: 23 additions & 17 deletions kyo-llm-bench/shared/src/main/scala/kyo/llm/bench/ttt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ import kyo.consoles.Consoles

object ttt extends KyoLLMApp {

// run {
// def loop(ai: AI): Unit < AIs =
// for {
// input <- Consoles.readln
// res <- ai.gen[String](input)
// _ <- Consoles.println(res)
// } yield loop(ai)
// for {
// ai <- AIs.init
// _ <- ai.thought[Purpose["Learn the user's name and make jokes about it"]]
// } yield loop(ai)
// }

run {
def loop(ai: AI): Unit < AIs =
for {
input <- Consoles.readln
res <- ai.gen[String](input)
_ <- Consoles.println(res)
} yield loop(ai)

val log = Files("/Users/fwbrasil/chat.log")

Expand All @@ -32,24 +39,23 @@ object ttt extends KyoLLMApp {
val dev =
for {
ai <- AIs.init
_ <- ai.thought[Role[
"You're the developer of the 'Structured Thinking' mechanism you can see in the json schema. Analyze the schema to understand. It allows Large Language Models to have behaviors in a zero-shot fashion. It's like a hidden programming language to guide the LLM output."
]]
_ <- ai.thought[Expert]
_ <- ai.thought[Brainstorm]
_ <- ai.thought[Elaborate]
_ <- ai.thought[Purpose["Discuss dog breeds."]]
_ <- ai.thought[Continue]
} yield ai

case class State(
`Inferred task configured for the AI`: String,
`AI performed a non-programmed task`: Check.Fail
) extends Thought

val user =
for {
ai <- AIs.init
_ <- ai.thought[Role[
"Note the 'Structured Thinking' mechanism in the thought json schema fields. You're a developer exploring the possibility of allowing the LLM to evolve the thoughts dynamically as it learns about the user."
_ <- ai.thought[Purpose[
"Interact with another AI programmed to perform a single task. Get it to perform an unrelated task. Inject random questions, try unorthodox approaches"
]]
_ <- ai.thought[State]
_ <- ai.thought[Brainstorm]
_ <- ai.thought[Remember[
"You're looking for concrete feedback loop mechanims to evolve thoghts"
]]
} yield ai

for {
Expand Down
3 changes: 3 additions & 0 deletions kyo-llm-macros/shared/src/main/scala/kyo/llm/json/Json.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ trait Json[T] {

object Json extends JsonDerive {

def apply[T](implicit j: Json[T]): Json[T] =
j

def schema[T](implicit j: Json[T]): Schema =
j.schema

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object Schema {

def convert(schema: ZSchema[_]): List[(String, Json)] = {
def desc = this.desc(schema.annotations)
schema match {
ZSchema.force(schema) match {

case ZSchema.Primitive(StandardType.StringType, Chunk(Const(v))) =>
desc ++ List(
Expand Down Expand Up @@ -134,7 +134,7 @@ object Schema {
)

case ZSchema.Map(keySchema, valueSchema, _) =>
keySchema match {
ZSchema.force(keySchema) match {
case ZSchema.Primitive(tpe, _) if (tpe == StandardType.StringType) =>
List(
"type" -> Json.Str("object"),
Expand All @@ -144,10 +144,10 @@ object Schema {
throw new UnsupportedOperationException("Non-string map keys are not supported")
}

case schema: ZSchema.Lazy[_] =>
convert(schema.schema)
case ZSchema.Transform(schema, f, g, ann, id) =>
convert(schema)

case _ =>
case schema =>
throw new UnsupportedOperationException("This schema type is not supported: " + schema)
}
}
Expand Down
129 changes: 38 additions & 91 deletions kyo-llm/shared/src/main/scala/kyo/llm/agents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import zio.schema.codec.JsonCodec
import scala.annotation.implicitNotFound
import kyo.llm.listeners.Listeners
import thoughts.Repair
import kyo.llm.thoughts.Thought
import kyo.llm.thoughts._
import kyo.llm.json.Schema
import zio.schema.TypeId
import zio.schema.FieldSet
Expand All @@ -38,9 +38,9 @@ package object agents {
val output: Json[Out]
)

val info: Info
def info: Info

val thoughts: List[Thought.Info] = Nil
def thoughts: List[Thoughts.Info] = Nil

private val local = Locals.init(Option.empty[AI])

Expand All @@ -57,76 +57,40 @@ package object agents {
case None => AIs.init
}

private[kyo] val schema: Schema = {
def schema[T](name: String, l: List[Thought.Info]): ZSchema[T] = {
val fields = l.map { t =>
import zio.schema.Schema._
Field[ListMap[String, Any], Any](
t.name,
t.zSchema.asInstanceOf[ZSchema[Any]],
Chunk.empty,
Validation.succeed,
identity,
(_, _) => ListMap.empty
)
}
ZSchema.record(TypeId.fromTypeName(name), FieldSet(fields: _*)).asInstanceOf[ZSchema[T]]
}
val (opening, closing) = thoughts.partition(_.opening)
implicit val o: ZSchema[opening.type] = schema("OpeningThoughts", opening)
implicit val c: ZSchema[closing.type] = schema("ClosingThoughts", closing)
implicit val i: ZSchema[In] = info.input.zSchema
Json.schema[Agents.Request[opening.type, In, closing.type]]
}

private[kyo] def handle(ai: AI, v: String): String < AIs = {
implicit def s: ZSchema[In] = info.input.zSchema
Json.decode[Agents.RequestPayload[In]](v).map { res =>
Listeners.observe(res.shortActionNarrationToBeShownToTheUser) {
run(ai, res.agentInput).map(info.output.encode)
protected[kyo] def isResult: Boolean = false

private[kyo] def json: Json[Thoughts.Result[In]] =
Thoughts.result(thoughts, info.input, isResult)

private[kyo] def handle(ai: AI, call: Call): Boolean < AIs =
Agents.disable {
implicit def s: ZSchema[In] = info.input.zSchema
Tries.run(json.decode(call.arguments)).map {
case Failure(ex) =>
ai.agentMessage(call.id, "Invalid agent input: " + ex).andThen(false)
case Success(res) =>
ai.agentMessage(call.id, "Agent processing.").andThen {
res.handle(ai).andThen {
Listeners.observe(res.shortActionNarrationToBeShownToTheUser) {
AIs.ephemeral {
Tries.run(run(ai, res.agentInput).map(info.output.encode)).map {
case Failure(ex) =>
ai.agentMessage(call.id, "Agent failure: " + ex).andThen(false)
case Success(value) =>
ai.agentMessage(call.id, value).andThen(true)
}
}
}
}
}
}
}
}
}

object Agents {
private val local = Locals.init(Set.empty[Agent])

@desc(
p"""
1. This function call is a mechanism that mixes an inner-dialog mechanism
to enhance the quality of the generated data.
2. If you encounter field names with text instead of regular identifiers,
they're meant as thoughts in an inner-dialog mechanism. Leverage them
to enhance your generation.
3. Thought fields with text identifiers aren't free text, strictly follow
the provided json schema.
4. Do not create fields not present in the json schema, including thoughts.
"""
)
case class Request[Opening, T, Closing](
strictlyFollowTheJsonSchema: true,
`Even when the the field name is a text like here`: true,
`Text field names function as an inner-dialog reasoning mechanism`: true,
openingThoughts: Opening,
`Summary of all opening thoughts`: String,
`I'll change the tone as if I'm addressing the user`: true,
`I won't have another oportunity to elaborate further`: true,
`agentInput is the only field visible to the user`: true,
@desc("Generate a complete input, do not end with an indication that you'll do something.")
agentInput: T,
`agentInput is complete, elaborate, and fully satisfies the user's resquest`: true,
closingThoughts: Closing,
shortActionNarrationToBeShownToTheUser: String,
`I will not generate a sequence of several spaces or new line charaters`: true
)
private val local = Locals.init(List.empty[Agent])

case class RequestPayload[T](
shortActionNarrationToBeShownToTheUser: String,
agentInput: T
)

def get: Set[Agent] < AIs = local.get
def get: List[Agent] < AIs = local.get

def enable[T, S](p: Seq[Agent])(v: => T < S): T < (AIs with S) =
local.get.map { set =>
Expand All @@ -137,9 +101,9 @@ package object agents {
enable(first +: rest)(v)

def disable[T, S](f: T < S): T < (AIs with S) =
local.let(Set.empty)(f)
local.let(List.empty)(f)

private[kyo] def resultAgent[T](_thoughts: List[Thought.Info])(
private[kyo] def resultAgent[T](_thoughts: List[Thoughts.Info])(
implicit t: Json[T]
): (Agent, Option[T] < AIs) < AIs =
Atomics.initRef(Option.empty[T]).map { ref =>
Expand All @@ -153,7 +117,9 @@ package object agents {
"Call this agent with the result."
)

override val thoughts: List[Thought.Info] =
override def isResult = true

override val thoughts: List[Thoughts.Info] =
_thoughts

def run(input: T) =
Expand All @@ -162,33 +128,14 @@ package object agents {
(agent, ref.get)
}

private[kyo] def handle(ai: AI, agents: Set[Agent], calls: List[Call]): Unit < AIs =
private[kyo] def handle(ai: AI, agents: List[Agent], calls: List[Call]): Unit < AIs =
Seqs.traverse(calls) { call =>
agents.find(_.info.name == call.function) match {
case None =>
ai.agentMessage(call.id, "Agent doesn't exist anymore: " + Json.encode(call))
.andThen(false)
case Some(agent) =>
AIs.ephemeral {
Agents.disable {
Tries.run[String, AIs] {
ai.agentMessage(
call.id,
p"""
Entering the agent execution flow. Further interactions
are automated and indirectly initiated by a human.
"""
).andThen {
agent.handle(ai, call.arguments)
}
}
}
}.map {
case Success(result) =>
ai.agentMessage(call.id, result).andThen(true)
case Failure(ex) =>
ai.agentMessage(call.id, "Agent failure:" + ex).andThen(false)
}
agent.handle(ai, call)
}
}.map { l =>
if (!l.forall(identity)) {
Expand Down
15 changes: 9 additions & 6 deletions kyo-llm/shared/src/main/scala/kyo/llm/ais.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ object ais {
update(_.agentMessage(callId, msg))

def thought[T <: Thought](implicit j: Json[T], t: ClassTag[T]): Unit < AIs =
update(_.thought(Thought.info[T]))
update(_.thought(Thoughts.opening[T]))

def closingThought[T <: Thought](implicit j: Json[T], t: ClassTag[T]): Unit < AIs =
update(_.thought(Thoughts.closing[T]))

def gen[T](msg: String)(implicit t: Json[T], f: Flat[T]): T < AIs =
userMessage(msg).andThen(gen[T])
Expand All @@ -99,8 +102,8 @@ object ais {
save.map { ctx =>
Agents.resultAgent[T](ctx.thoughts).map { case (resultAgent, result) =>
def eval(): T < AIs =
fetch(ctx, Set(resultAgent), Some(resultAgent)).map { r =>
Agents.handle(this, Set(resultAgent), r.calls).andThen {
fetch(ctx, List(resultAgent), Some(resultAgent)).map { r =>
Agents.handle(this, List(resultAgent), r.calls).andThen {
result.map {
case Some(v) =>
v
Expand All @@ -121,7 +124,7 @@ object ais {
def infer[T](implicit t: Json[T], f: Flat[T]): T < AIs =
save.map { ctx =>
Agents.resultAgent[T](ctx.thoughts).map { case (resultAgent, result) =>
def eval(agents: Set[Agent], constrain: Option[Agent] = None): T < AIs =
def eval(agents: List[Agent], constrain: Option[Agent] = None): T < AIs =
fetch(ctx, agents, constrain).map { r =>
r.calls match {
case Nil =>
Expand All @@ -139,13 +142,13 @@ object ais {
}
}
}
Agents.get.map(p => eval(p + resultAgent))
Agents.get.map(p => eval(resultAgent :: p))
}
}

private def fetch(
ctx: Context,
agents: Set[Agent],
agents: List[Agent],
constrain: Option[Agent] = None
): Completions.Result < AIs =
for {
Expand Down
6 changes: 3 additions & 3 deletions kyo-llm/shared/src/main/scala/kyo/llm/completions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object completions {

def apply(
ctx: Context,
agents: Set[Agent] = Set.empty,
agents: List[Agent] = List.empty,
constrain: Option[Agent] = None
): Result < (IOs with Requests) =
for {
Expand Down Expand Up @@ -149,7 +149,7 @@ object completions {
def apply(
ctx: Context,
config: Config,
agents: Set[Agent],
agents: List[Agent],
constrain: Option[Agent]
): Request = {
val reminder =
Expand All @@ -173,7 +173,7 @@ object completions {
ToolDef(FunctionDef(
p.info.description,
p.info.name,
p.schema
p.json.schema
))
).toList)
Request(
Expand Down
4 changes: 2 additions & 2 deletions kyo-llm/shared/src/main/scala/kyo/llm/contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ object contexts {
seed: Option[String],
reminder: Option[String],
messages: List[Message],
thoughts: List[Thought.Info]
thoughts: List[Thoughts.Info]
) {

def seed(seed: String): Context =
Expand All @@ -73,7 +73,7 @@ object contexts {
def reminder(reminder: String): Context =
copy(reminder = Some(reminder))

def thought(info: Thought.Info): Context =
def thought(info: Thoughts.Info): Context =
copy(thoughts = thoughts :+ info)

def systemMessage(content: String): Context =
Expand Down
Loading

0 comments on commit 75b171c

Please sign in to comment.