-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JsonProcessor does not work with add_tools
#213
Comments
Hi @jeroenbourgois , could you please add more details (e.g., a stack trace) or a toy reproducible example? |
Hi @jeroenbourgois! Interesting. It should not cause an error, but the two aren't intended to be used together. If you are working with a model that supports tool calls, you don't need a JSON processor. The JsonProcessor is really intended for models that don't support tools but can give you JSON in a text response. The response may also have text around it like, "Here's your result in JSON:" and the JsonProcessor can help pluck out and parse the JSON parts from the text. If you're using a model with tools, that is a more reliable way to get structured data extraction. |
If you can provide some additional details or example errors, I think it's worth trying to make the dev experience clearer and smoother. Thanks! |
@brainlid that's more then fair, I will try to set up a demo project this weekend! But elaborating on your initial response, you say that if you are working with a model that support tool calls, you don't need a JSON processor? I am having trouble to understand that - being also relatively new to the whole LangChain concept. I use the tool to allow the model to answer a very context specific question. The chat config itself requests Should I do something different when using tools then? |
@brainlid I have put a simple example on Github: https://github.com/jeroenbourgois/langchain_json_preprocessor/blob/main/lib/langchain_json_processor.ex So running the module will give an error:
If you comment out this line regarding the preprocessor it will work. The final output is:
So, to circle back to my previous response I am left with two questions:
PS: thank you for the library, working with the custom functions and also getting the verbose output during dev is so cool! |
When you define a tool, you define the The When the LLM executes the tool, it creates an assistant This all means that if the LLM natively supports tools and functions, then all the conversion from JSON text into an Elixir map is handled for you and is available for processing in the Function's Elixir code. Hopefully this guide is up-to-date: https://hexdocs.pm/langchain/0.3.0-rc.1/custom_functions.html |
Thanks for the example!
I understand now what you were seeing. The LLM was executing a Function. The result of that function was being returned to the LLM and it "told" you what was returned. Partly because you instructed it to return the results as JSON. Then you were trying to parse out the returned JSON results. There is a more direct way to do what I think you want that should work better for you. See my gist. I just published I created an updated version in this gist: |
@brainlid ok thank you for the gist and the clarification about the functions. We are looking at using a JSON schema for all output, so the preprocessor might not be needed after all. However, given I would still like to use it... Although I understand functions will cause the LLM to respond with JSON directly for the answer to that function, what if the final result differs from the called function result? Then I would maybe still make sense to use the In the gist you supplied (again: thank you for taking the time and effort!), I see how you output the result of the tool call. This is a good help, but - at least in our case - this is not the final result we are looking for, as the LLM does some manipulations after retrieving a result from the function.
So in your gist, the output we get is
I don't know if what I am explaining is getting across good, but in essence: the tooling might just be a small step for the LLM to take within the total response. In those cases it might still be useful to use the JSON processor? |
Thanks for taking the time to bring me up to speed with what you're doing. I get your point. It is valid for the LLM to execute a function in your application before giving a final JSON formatted response. That makes sense. Yes, we should support that use case. As a side-note, I started a ChangesetProcessor that I haven't gotten back to. I didn't get far enough where there's anything valuable to share. The idea is it uses the JsonProcessor to get it into a JSON format, then it processes it through an Ecto changeset where you may have additional validation checks that you care about. The real benefit is getting it into an Elixir struct format and enforcing other validations like length, enum, etc. That approach might be helpful too. But to your point, yes, I see the need for the JsonProcessor also working in addition to tools. The fact that they don't both work is probably a bug. NOTE: I don't know if OpenAI can have tools and a specified response_format and have them both work. 🤔 It depends on how strictly it enforces it. Like it can't call a tool if the response format takes precedence. Interesting. I'd love to hear how your experiments go! UPDATE: See #215 UPDATE 2: I'll have to think and look into it more for having a JsonProcessor work in a conversation with tool calls. Because the JsonProcessor will be run on the assistant response with the tool call and there's not JSON in the text content to process, so it that an error? Or does it pass through with no JSON being processed? Unsure. |
@brainlid thank you so much for your elaborate response! For now, we have removed the tool calls in our application by splitting the task into several separate, smaller prompts. We were able to do the logic of the function outside the LLM. After removing the tool calls I could apply the JsonProcessor again and there was no CoT anymore. The LLM responded with near perfect JSON which was then put on the So for now, we are good. Feel free to close the issue, but maybe mention it in the docs that what I initially did is not expected? Note: it was my co-founder who pointed out the possible CoT in the messages when using prompts, which was not that surprising coming to think about it. But this also leads to slightly leads predicable output. Without the tool calls it is much more succinct, being mostly limited to outputting JSON. We think that tool calls can be very useful if the output of that tool result is used, just as in your gist earlier. |
I have a fairly basic example. When I run the chain like below, with a custom function, everything is executed.
However, if I add
LLMChain.message_processors([JsonProcessor.new!(~r/```json(.*?)```/s)])
just before therun
I get an error. If then I remove theadd_tools
call it works again. Maybe the result theadd_tools
function is putting on the chain does not work nicely with what theJsonProcessor
expects?The text was updated successfully, but these errors were encountered: