diff --git a/kyo-llm-bench/shared/src/main/scala/kyo/llm/bench/ttt.scala b/kyo-llm-bench/shared/src/main/scala/kyo/llm/bench/ttt.scala index dd3331cd5..6d4519a44 100644 --- a/kyo-llm-bench/shared/src/main/scala/kyo/llm/bench/ttt.scala +++ b/kyo-llm-bench/shared/src/main/scala/kyo/llm/bench/ttt.scala @@ -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") @@ -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 { diff --git a/kyo-llm-macros/shared/src/main/scala/kyo/llm/json/Json.scala b/kyo-llm-macros/shared/src/main/scala/kyo/llm/json/Json.scala index 7bca49a88..bca13ebd7 100644 --- a/kyo-llm-macros/shared/src/main/scala/kyo/llm/json/Json.scala +++ b/kyo-llm-macros/shared/src/main/scala/kyo/llm/json/Json.scala @@ -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 diff --git a/kyo-llm-macros/shared/src/main/scala/kyo/llm/json/JsonSchema.scala b/kyo-llm-macros/shared/src/main/scala/kyo/llm/json/JsonSchema.scala index c16338c03..484744566 100644 --- a/kyo-llm-macros/shared/src/main/scala/kyo/llm/json/JsonSchema.scala +++ b/kyo-llm-macros/shared/src/main/scala/kyo/llm/json/JsonSchema.scala @@ -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( @@ -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"), @@ -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) } } diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/agents.scala b/kyo-llm/shared/src/main/scala/kyo/llm/agents.scala index b3a80ecda..98beff62c 100644 --- a/kyo-llm/shared/src/main/scala/kyo/llm/agents.scala +++ b/kyo-llm/shared/src/main/scala/kyo/llm/agents.scala @@ -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 @@ -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]) @@ -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 => @@ -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 => @@ -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) = @@ -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)) { diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/ais.scala b/kyo-llm/shared/src/main/scala/kyo/llm/ais.scala index da0dee38f..160bd5853 100644 --- a/kyo-llm/shared/src/main/scala/kyo/llm/ais.scala +++ b/kyo-llm/shared/src/main/scala/kyo/llm/ais.scala @@ -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]) @@ -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 @@ -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 => @@ -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 { diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/completions.scala b/kyo-llm/shared/src/main/scala/kyo/llm/completions.scala index 735d741cf..edae08a30 100644 --- a/kyo-llm/shared/src/main/scala/kyo/llm/completions.scala +++ b/kyo-llm/shared/src/main/scala/kyo/llm/completions.scala @@ -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 { @@ -149,7 +149,7 @@ object completions { def apply( ctx: Context, config: Config, - agents: Set[Agent], + agents: List[Agent], constrain: Option[Agent] ): Request = { val reminder = @@ -173,7 +173,7 @@ object completions { ToolDef(FunctionDef( p.info.description, p.info.name, - p.schema + p.json.schema )) ).toList) Request( diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/contexts.scala b/kyo-llm/shared/src/main/scala/kyo/llm/contexts.scala index d6c60ff62..69d990122 100644 --- a/kyo-llm/shared/src/main/scala/kyo/llm/contexts.scala +++ b/kyo-llm/shared/src/main/scala/kyo/llm/contexts.scala @@ -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 = @@ -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 = diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts.scala b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts.scala new file mode 100644 index 000000000..3bd49fae5 --- /dev/null +++ b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts.scala @@ -0,0 +1,146 @@ +package kyo.llm + +import kyo._ +import kyo.concurrent.fibers.Fibers +import kyo.llm.ais._ +import kyo.llm.json.Schema +import kyo.llm.json._ +import kyo.llm.thoughts.Thoughts.Collect +import kyo.llm.thoughts._ +import kyo.seqs.Seqs +import kyo.stats.Stats +import zio.Chunk +import zio.schema.FieldSet +import zio.schema.Schema.Field +import zio.schema.TypeId +import zio.schema.validation.Validation +import zio.schema.{Schema => ZSchema} + +import scala.collection.immutable.ListMap +import scala.reflect.ClassTag +import kyo.consoles.Consoles + +package object thoughts { + + abstract class Thought { + self: Product => + + def name = productPrefix + + final def handle(ai: AI): Unit < AIs = + handle(Thoughts.Empty, "", ai) + + final def handle(parent: Thought, field: String, ai: AI): Unit < AIs = { + eval(parent, field, ai).andThen { + (0 until productArity).flatMap { idx => + val name = productElementName(idx) + productElement(idx) match { + case Collect(l) => + l.map((name, _)) + case v: Thought => + (name, v) :: Nil + case _ => + None + } + } + }.map { thoughts => + Seqs.traverse(thoughts)((f, t) => t.handle(self, f, ai)).unit + } + } + + def eval(parent: Thought, field: String, ai: AI): Unit < AIs = () + + } + + object Thoughts { + + sealed trait Position + object Position { + case object Opening extends Position + case object Closing extends Position + } + + case object Empty extends Thought + case class Collect(l: List[Thought]) extends Thought + + private[kyo] val stats = Stats.initScope("thoughts") + + case class Info( + name: String, + pos: Position, + json: Json[_] + ) + + def opening[T <: Thought](implicit j: Json[T], t: ClassTag[T]): Info = + Info(t.runtimeClass.getSimpleName(), Position.Opening, j) + + def closing[T <: Thought](implicit j: Json[T], t: ClassTag[T]): Info = + Info(t.runtimeClass.getSimpleName(), Position.Closing, j) + + def result[T](thoughts: List[Info], j: Json[T], full: Boolean): Json[Result[T]] = { + def schema[T](name: String, l: List[Thoughts.Info]): ZSchema[T] = { + val fields = l.map { t => + import zio.schema.Schema._ + Field.apply[ListMap[String, Any], Any]( + t.name, + t.json.zSchema.asInstanceOf[ZSchema[Any]], + Chunk.empty, + Validation.succeed, + identity, + (_, _) => ListMap.empty + ) + } + val r = ZSchema.record(TypeId.fromTypeName(name), FieldSet(fields: _*)) + r.transform( + listMap => Collect(listMap.values.toList.asInstanceOf[List[Thought]]), + _ => ListMap.empty + ).asInstanceOf[ZSchema[T]] + } + val (opening, closing) = thoughts.partition(_.pos == Position.Opening) + type Opening + type Closing + implicit val o: ZSchema[Opening] = schema("OpeningThoughts", opening) + implicit val c: ZSchema[Closing] = schema("ClosingThoughts", closing) + implicit val t: ZSchema[T] = j.zSchema + (if (full) { + Json[Result.Full[Opening, T, Closing]] + } else { + Json[Result.Short[Opening, T]] + }).asInstanceOf[Json[Result[T]]] + } + + sealed trait Result[T] extends Thought { + self: Product => + val agentInput: T + val shortActionNarrationToBeShownToTheUser: String + } + + object Result { + + case class Full[Opening, T, Closing]( + strictlyFollowTheJsonSchema: Boolean, + `Always use the correct json type from the schema`: Boolean, + `This is a required thought field for inner-dialog`: Boolean, + `Strictly follow the required fields including thoughts`: Boolean, + openingThoughts: Opening, + `Opening thoughts summary`: String, + `Only agentInput is visible to the user`: Boolean, + agentInput: T, + `agentInput fully satisfies the user's resquest`: Boolean, + shortActionNarrationToBeShownToTheUser: String, + closingThoughts: Closing, + allFieldsAdhereToTheJsonSchema: Boolean + ) extends Result[T] + + case class Short[Thoughts, T]( + `I won't skip any of the required fields`: Boolean, + openingThoughts: Thoughts, + `Opening thoughts summary`: String, + agentInput: T, + `agentInput fully satisfies the user's resquest`: Boolean, + shortActionNarrationToBeShownToTheUser: String, + allFieldsAdhereToTheJsonSchema: Boolean + ) extends Result[T] + } + } +} diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Check.scala b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Check.scala index 59067e05a..cbe88b717 100644 --- a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Check.scala +++ b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Check.scala @@ -9,113 +9,82 @@ import kyo.ios.IOs object Check { - private val stats = Thought.stats.scope("check") + private val stats = Thoughts.stats.scope("check") private val success = stats.initCounter("success") private val failure = stats.initCounter("failure") - case class CheckFailed(ai: AI, thought: Thought, invariant: String, analysis: String) - extends RuntimeException - - private def observe(parent: Thought, field: String, result: Boolean) = { - val c = if (result) success else failure - c.attributes( - Attributes - .add("thought", parent.name) - .add("field", field) - ).inc - } - - private def warn( - ai: AI, - parent: Thought, + case class CheckFailed( + thought: Thought, field: String, - invariant: String, - analysis: String = "Plase reason about the failure and fix any mistakes.", - repair: Option[Repair] = None - ): Unit < AIs = - ai.systemMessage( - p""" - Thought Invariant Failure - ========================= - Thought: ${parent.name} - Field: $field - Description: $invariant - Analysis: $analysis - ${repair.map(pprint(_).plainText).map("Repair: " + _).getOrElse("")} - """ - ) - - case class Info(result: Boolean) extends Thought { - override def eval(parent: Thought, field: String, ai: AI) = - observe(parent, field, result) - } - - object Info { - implicit val schema: ZSchema[Info] = - ZSchema.primitive[Boolean].transform(Info(_), _.result) + analysis: String + ) extends RuntimeException { + override def toString = + p""" + Thought Invariant Failure + ========================= + Thought: ${thought.name} + Field: $field + Analysis: $analysis + **Please take all corrective measures to avoid another failure** + """ } - case class Warn[Invarant <: String]( - `Invariant check description`: Invarant, - `Invariant holds`: Boolean + case class Info( + `The outer field name is an invariant description`: Boolean, + `Analyze if the invariant has been violated`: String, + invariantViolated: Boolean ) extends Thought { override def eval(parent: Thought, field: String, ai: AI) = - observe(parent, field, `Invariant holds`).andThen { - warn(ai, parent, field, `Invariant check description`) - } + observe(parent, field, !invariantViolated) } - case class Fail[Invarant <: String]( - `Invariant check description`: Invarant, - `Invariant check analysis`: String, - `Invariant holds`: Boolean + case class Warn( + `The outer field name is an invariant description`: Boolean, + `Analyze if the invariant has been violated`: String, + invariantViolated: Boolean ) extends Thought { override def eval(parent: Thought, field: String, ai: AI) = - observe(parent, field, `Invariant holds`).andThen { - warn( - ai, - parent, - field, - `Invariant check description`, - `Invariant check analysis` - ).andThen { - IOs.fail(CheckFailed( - ai, - parent, - `Invariant check description`, - `Invariant check analysis` - )) + observe(parent, field, !invariantViolated).andThen { + if (!invariantViolated) { + () + } else { + ai.systemMessage( + CheckFailed( + parent, + field, + `Analyze if the invariant has been violated` + ).toString + ) } } } - case class SelfRepair[Invarant <: String]( - `Invariant check description`: Invarant, - `Invariant check analysis`: String, - `Invariant holds`: Boolean + case class Fail( + `The outer field name is an invariant description`: Boolean, + `Analyze if the invariant has been violated`: String, + invariantViolated: Boolean ) extends Thought { override def eval(parent: Thought, field: String, ai: AI) = - observe(parent, field, `Invariant holds`).andThen { - AIs.ephemeral { - warn( - ai, - parent, - field, - `Invariant check description`, - `Invariant check analysis` - ).andThen { - ai.gen[Repair]("Provide a repair for the last failed thought invariant.") - } - }.map { repair => - warn( - ai, + observe(parent, field, !invariantViolated).andThen { + if (!invariantViolated) { + () + } else { + IOs.fail(CheckFailed( parent, field, - `Invariant check description`, - `Invariant check analysis`, - Some(repair) - ) + `Analyze if the invariant has been violated` + )) } } } + + private def observe(parent: Thought, field: String, result: Boolean) = { + val c = if (result) success else failure + c.attributes( + Attributes + .add("thought", parent.name) + .add("field", field) + ).inc + } + } diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Continue.scala b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Continue.scala new file mode 100644 index 000000000..7d5515939 --- /dev/null +++ b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Continue.scala @@ -0,0 +1,6 @@ +package kyo.llm.thoughts + +case class Continue( + `Always continue the conversation`: Boolean, + `Don't thank the user`: Boolean +) extends Thought diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Observe.scala b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Observe.scala index 25e0ca534..010ccd7c6 100644 --- a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Observe.scala +++ b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Observe.scala @@ -5,10 +5,11 @@ import zio.schema.{Schema => ZSchema} import kyo._ import kyo.ios._ import kyo.stats._ +import kyo.logs.Logs object Observe { - private val stats = Thought.stats.scope("observe") + private val stats = Thoughts.stats.scope("observe") private val count = stats.initCounter("count") private val sum = stats.initCounter("sum") private val hist = stats.initHistogram("distribution") @@ -53,4 +54,54 @@ object Observe { implicit val schema: ZSchema[Distribution] = ZSchema.primitive[Double].transform(Distribution(_), _.v) } + + object Log { + + private def log(f: String => Unit < IOs, parent: Thought, field: String, s: String) = + if (s.nonEmpty) { + f(s"(thought=${parent.name}, field=$field): $s") + } else { + () + } + + case class Debug(s: String) extends Thought { + override def eval(parent: Thought, field: String, ai: AI) = + log(Logs.debug, parent, field, s) + } + + object Debug { + implicit val schema: ZSchema[Debug] = + ZSchema.primitive[String].transform(Debug(_), _.s) + } + + case class Info(s: String) extends Thought { + override def eval(parent: Thought, field: String, ai: AI) = + log(Logs.info, parent, field, s) + } + + object Info { + implicit val schema: ZSchema[Info] = + ZSchema.primitive[String].transform(Info(_), _.s) + } + + case class Warn(s: String) extends Thought { + override def eval(parent: Thought, field: String, ai: AI) = + log(Logs.warn, parent, field, s) + } + + object Warn { + implicit val schema: ZSchema[Warn] = + ZSchema.primitive[String].transform(Warn(_), _.s) + } + + case class Error(s: String) extends Thought { + override def eval(parent: Thought, field: String, ai: AI) = + log(Logs.error, parent, field, s) + } + + object Error { + implicit val schema: ZSchema[Error] = + ZSchema.primitive[String].transform(Error(_), _.s) + } + } } diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Purpose.scala b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Purpose.scala new file mode 100644 index 000000000..d79a198b6 --- /dev/null +++ b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Purpose.scala @@ -0,0 +1,9 @@ +package kyo.llm.thoughts + +case class Purpose[T <: String]( + `My single purpose is`: T, + `Don't approach any other subject`: Boolean, + `Any answer must be related to purpose`: Boolean, + `Do not stop until purpose is fulfilled`: Boolean, + `Strategy to act only according to purpose`: String +) extends Thought diff --git a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Thought.scala b/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Thought.scala deleted file mode 100644 index d9f06c689..000000000 --- a/kyo-llm/shared/src/main/scala/kyo/llm/thoughts/Thought.scala +++ /dev/null @@ -1,56 +0,0 @@ -package kyo.llm.thoughts - -import kyo._ -import kyo.llm.ais._ -import zio.schema.{Schema => ZSchema} -import scala.reflect.ClassTag -import kyo.concurrent.fibers.Fibers -import kyo.stats.Stats - -abstract class Thought { - self: Product => - - def name = productPrefix - - final def handle(parent: Thought, field: String, ai: AI): Unit < AIs = { - val thoughts = - eval(parent, field, ai).andThen { - (0 until productArity).flatMap { idx => - productElement(idx) match { - case v: Thought => - Some((productElementName(idx), v)) - case _ => - None - } - } - } - AIs.parallelTraverse(thoughts)((f, t) => t.eval(self, f, ai)).unit - } - - def eval(parent: Thought, field: String, ai: AI): Unit < AIs = () - -} - -object Thought { - - val stats = Stats.initScope("thoughts") - - abstract class Closing extends Thought { - self: Product => - } - - sealed trait Info { - type Thought - def name: String - def opening: Boolean - def zSchema: ZSchema[Thought] - } - - def info[T <: Thought](implicit j: Json[T], t: ClassTag[T]): Info = - new Info { - type Thought = T - val name = t.runtimeClass.getSimpleName() - val opening = !classOf[Closing].isAssignableFrom(t.runtimeClass) - val zSchema = j.zSchema - } -}