ScalablyTyped/Converter

LangchainJS conversion: cannot subclass "Tool"

Quafadas opened this issue · 6 comments

Full disclosure : I'm filling this as an issue here, on a "best guess" - being unsure of exactly cause / scope of ST.

TL:DR:
I'd like to be able to subclass langchainJS "Tool" classes generated ST facad. I can't due to a compiler error, that I do not believe I can easily work around. I believe the compiler error to be a function of the generated facade.

Notes:
I think langchainJS makes quite some use of "zod". I don't really know what "zod" is, but I think it doesn't help ST at all.

Here is the ST conversion that I'm struggling with.

 /* note: abstract class */ @JSImport("langchain/dist/tools/base", "StructuredTool")
  @js.native
  open class StructuredTool[T /* <: /* import warning: transforms.QualifyReferences#resolveTypeRef many Couldn't qualify z.ZodObject<any, any, any, any> */ Any */] () extends BaseLangChain {
    def this(fields: ToolParams) = this()
    
    /* protected */ def _call(
      arg: /* import warning: transforms.QualifyReferences#resolveTypeRef many Couldn't qualify z.output<T> */ Any
    ): js.Promise[String] = js.native
    /* protected */ def _call(
      arg: /* import warning: transforms.QualifyReferences#resolveTypeRef many Couldn't qualify z.output<T> */ Any,
      runManager: CallbackManagerForToolRun
    ): js.Promise[String] = js.native
    
    def call(
      arg: /* import warning: importer.ImportType#apply Failed type conversion: / * import warning: transforms.QualifyReferences#resolveTypeRef many Couldn't qualify z.output<T> * / any extends string ? string : never */ js.Any
    ): js.Promise[String] = js.native
    def call(
      arg: /* import warning: importer.ImportType#apply Failed type conversion: / * import warning: transforms.QualifyReferences#resolveTypeRef many Couldn't qualify z.output<T> * / any extends string ? string : never */ js.Any,
      callbacks: Callbacks
    ): js.Promise[String] = js.native
    def call(
      arg: /* import warning: transforms.QualifyReferences#resolveTypeRef many Couldn't qualify z.input<T> */ Any
    ): js.Promise[String] = js.native
    def call(
      arg: /* import warning: transforms.QualifyReferences#resolveTypeRef many Couldn't qualify z.input<T> */ Any,
      callbacks: Callbacks
    ): js.Promise[String] = js.native
    
    var description: String = js.native
    
    var name: String = js.native
    
    var returnDirect: Boolean = js.native
    
    var schema: T | (/* import warning: transforms.QualifyReferences#resolveTypeRef many Couldn't qualify z.ZodEffects<T> */ Any) = js.native
  }

When trying to subclass it, my implementation

  class EchoTool extends StructuredTool[StringDictionary[js.Any]]:
    name = "Echo"
    description = "Will return the same string you gave it, it's genius!"
    override def call(arg: js.Any): Promise[String] = ???

  end EchoTool

Provides this compiler error.

[info] compiling 1 Scala source to /Users/simon/Code/mill-full-stack/mill-full-stack/out/frontend/compile.dest/classes ...
[error] -- Error: /Users/simon/Code/mill-full-stack/mill-full-stack/frontend/src/chat.page.scala:122:17 
[error] 122 |    override def call(arg: js.Any): Promise[String] = ???
[error]     |                 ^
[error]     |              Cannot disambiguate overloads for method call with types
[error]     |                (arg: Object): scala.scalajs.js.Promise
[error]     |                (arg: scala.scalajs.js.Any): scala.scalajs.js.Promise
[error] -- Error: /Users/simon/Code/mill-full-stack/mill-full-stack/frontend/src/chat.page.scala:119:8 
[error] 119 |  class EchoTool extends StructuredTool[StringDictionary[js.Any]]:
[error]     |        ^
[error]     |Cannot disambiguate overloads for method call with types
[error]     |  (arg: Object, callbacks: scala.scalajs.js.Object): scala.scalajs.js.Promise
[error]     |  (arg: scala.scalajs.js.Any, callbacks: scala.scalajs.js.Object): 
[error]     |  scala.scalajs.js.Promise
[error] two errors found
1 targets failed
frontend.compile Compilation failed

I posted this on discord briefly, where a brief discussion yielded little insight. My suspicion, is that the nature of the underlying typescript, may be rather awkward. If this it "not in scope" of ST, that may be a reasonable answer.

https://github.com/hwchase17/langchainjs/blob/main/langchain/src/tools/base.ts

Please note, I'm not super confident in my own diagnosis.

Hey there, thanks for you interest in ST.

This is an absolute minefield. As you can see, there are a bunch of overloads of that method in the typescript and generated scala.js code. All these overloads point to the same method in Javascript. Typescript knows how to handle this, but it's very far beyond the capabilities of Scala. It's just a completely different world. ST manages to generate code so you can call these methods, but implementing them is another matter entirely.

The solution? copy/paste StructuredTool into your codebase, delete all the overloads, specify a version of call which is what you need, subclass that.

edit: you'll likely need a cast at some point too, but casts are ok in this world :)

I should note that it wouldn't be impossible to output code which could more easily be extended, but then calling these methods would suffer. Subclassing is such an infrequent thing in javascript that I think it makes sense to focus on the normal usecase, which is newing classes and calling things on them.

I can't believe I got thanked for this discussion! So firstly - Rather a thank you for ST - I use it quite a bit, it's wonderful.

This is an absolute minefield.

Heh - I feared this.

The solution? copy/paste StructuredTool into your codebase, delete all the overloads, specify a version of call which is what you need, subclass that.

I had actually already started walking this route, and I'll continue trying. I think things get weird in the class definition, because it leans into Zod. Definitely outside of my comfort zone.

Subclassing is such an infrequent thing in javascript that I think it makes sense to focus on the normal usecase, which is newing classes and calling things on them.

Absolutely no objections :-).

Thanks for writing back. I'll close this - based on everything you've written above, I think it's fair to say that this falls into some sort of "don't fix" / "expected behaviour" sort of category.

I don't even get it to translate

[error] ZincCompiler.scala:230 msg.get() [Error] ~/target/streams/_global/stImport/_global/streams/sources/z/zod/src/main/scala/typings/zod/libZodErrorMod/package.scala:46: illegal cyclic reference involving type ZodFormattedError [project => langchainscala, ms => 72469]
[error] ZincCompiler.scala:230 msg.get() one error found [project => langchainscala, ms => 72674]
[error] Phase3Compile.scala:152 err Compilation failed: -Xplugin:~/Caches/Coursier/v1/https/repo1.maven.org/maven2/org/scala-js/scalajs-compiler_2.13.11/1.13.1/scalajs-compiler_2.13.11-1.13.1.jar, -encoding, utf-8, -feature, -g:notailcalls, -language:implicitConversions, -language:higherKinds, -language:existentials, -bootclasspath, ~/langchainscala/target/streams/_global/stImport/_global/streams/sources/z/zod/src/main/scala/typings/zod/libZodErrorMod/package.scala:46: illegal cyclic reference involving type ZodFormattedError [thread => 112, project => langchainscala, ms => 72827, phase => build, id => zod, flavour => NormalFlavour]

Try adding “Zod” To the list of excluded libraries.

But be warned - this is the tip of a deep iceberg…

https://www.reddit.com/r/scala/comments/14a71zj/is_there_any_project_on_langchain_with_scala/jo9t96q/?utm_source=share&utm_medium=ios_app&utm_name=ioscss&utm_content=1&utm_term=1&context=3

If you succeed in reducing the impedance - please don’t be shy about sharing!

Yikes.