feat(AI EXTRACT):add llm AI EXTRACT feature - #60
Conversation
Signed-off-by: ericyuanhui <285521263@qq.com>
|
Modifications for this feature also require minor corresponding changes in Ladybug Core to work in tandem. Once this extension is merged, I will submit a PR to Ladybug Core promptly. |
|
The root cause lies in ai_extract.cpp invoking the newly added overload TaskScheduler::scheduleTaskAndWaitOrError(Task, ClientContext*, bool). The CI picks up an older version of Ladybug Core, whose header only declares the legacy variant accepting ExecutionContext*. Please refer to the Core content in this link LadybugDB/ladybug#818 . @adsharma |
|
Two concerns here:
Even though it's in an extension, I'm not sure maintaining this code and keeping up with the progress from the ecosystem is easy. |
I found out ladybug has llm extension. but it embedding function. So I follow duckdb extension. It has https://duckdb.org/community_extensions/extensions/ai this extension. I think it is ok for me. I add ai extract feature on ladybug extension. The functionality I want to implement is to read the contents of a specific column in a Ladybug table and call an LLM row by row, using a prompt to extract the required information. I have already verified that this approach works, and I have also added concurrent processing. If additional AI capabilities are needed in the future, such as those provided by DuckDB-AI, we could continue adding our own AI functions within the same framework. The accuracy and correctness of the extracted data should not be concerns for Ladybug itself; those depend on the capabilities of the LLM. Ladybug would essentially act as a caller, taking data from its own tables and invoking the LLM on a row-by-row basis for extraction. Since this is not a core capability of the graph database engine, I implemented it at the extension layer. However, if we use As for ExtractBench, it is an evaluation benchmark for assessing the capabilities of LLMs. It should not necessarily be considered a requirement that our implementation itself must satisfy. In fact, integrating these AI capabilities directly into the core might be more convenient for production use, since extensions require additional steps such as loading them. |
|
Using |
|
Feel free to keep the discussion going. We are sure we can reach the most ideal state. The prior implementation draws inspiration from DuckDB. @adsharma |
Signed-off-by: ericyuanhui <285521263@qq.com>
|
@adsharma hello ? Any comments? |
|
@ericyuanhui I'm convinced by your arguments. Can merge this after the tests pass. Like you say this is pre-existing code. I'm not convinced that we should maintain this LLM interfacing code in a extension repo. I expect there to be better solutions elsewhere. We should use a third-party lib when the ecosystem is mature enough. |
|
Databricks has similar functionality: https://docs.databricks.com/aws/en/sql/language-manual/functions/ai_extract |
| SET(OPENSSL_USE_STATIC_LIBS FALSE) | ||
| # OpenSSL is resolved by the top-level build before extensions are configured. | ||
| # LBUG_REQUIRE_STATIC_OPENSSL must therefore be set at the top level. | ||
| find_package(OpenSSL REQUIRED) |
There was a problem hiding this comment.
Keep the security sensitive libs such as openssl and curl dynamic, so users can keep them updated and we're not responsible for shipping fixes.
Signed-off-by: ericyuanhui <285521263@qq.com>
Okay, I will keep following up on this project. Should a more complete solution become available, I will actively participate in revisions and refactoring. Thank you. @adsharma |
Ladybug Cypher API
The API is scalar: every input graph row produces one
STRINGresult.The same scalar expression works for node and relationship properties. These
examples assume a mock OpenAI-compatible endpoint that returns the shown JSON
for the corresponding input.
Expected result:
Minimal Mock OpenAI-Compatible Server
The examples above require a local mock service;
LOAD EXTENSION llmdoes notstart one. In another terminal, run this Python 3 command to listen on
127.0.0.1:18080:Keep that terminal running before executing the Cypher examples. Press
Ctrl+Cto stop the mock service; subsequent calls will reportCouldn't connect to server. The mock accepts any Bearer key, but callers muststill provide the required
api_keyargument. It returns fixed JSON matchingthe Person and Transfer examples in this document.
Expected result:
The provider is only
openai_compatiblein this phase. Model names are opaquestrings passed to the endpoint. The endpoint must expose:
This permits OpenAI itself plus compatible gateways such as vLLM, LiteLLM, and
llama.cpp servers configured with a compatible
/v1base URL.Request, Credentials, And Result
The adapter sends a non-streaming request in this shape:
{ "model": "gpt-4o-mini", "messages": [ {"role": "system", "content": "Follow the extraction instruction exactly."}, {"role": "user", "content": "Text:\n...\n\nInstruction:\n..."} ], "temperature": 0 }The system content and user content are generated by
AI_EXTRACT; callers donot provide arbitrary system messages in phase 1. The fixed envelope is:
The source delimiters are prompt structure, not a security boundary; the model
still receives untrusted source text. A future hardened prompt policy can add
provider-specific structured-output controls.
It reads the generated value from
choices[0].message.contentand returns itas Ladybug
STRING.api_keyis the required thirdSTRINGargument. Everyinvocation and every non-NULL input row uses that value for
Authorization: Bearer <api_key>. NULL or empty keys are rejected before therequest. The extension does not read
OPENAI_API_KEY/LLM_API_KEY, use anenvironment fallback, or use
CREATE SECRET.The argument is required even when a local mock endpoint does not authenticate.
The completion adapter uses libcurl with a 120-second total-request timeout and
a connection timeout of
min(10 seconds, total timeout), matching DuckDB AI.Both limits are capped by the remaining Ladybug query timeout where possible;
they are not the existing embedding path's separate
cpp-httplibread/writetimeouts.
Functional Requirements
text,instruction, andapi_keyto beSTRING; provider, model, andendpoint must be bind-time string literals when supplied.
openai_compatible, model to the documentedgpt-4o-minidefault, and endpoint tohttps://api.openai.com/v1.POST <base_url>/chat/completions, Bearer authentication, andContent-Type: application/json.api_key, malformed URL, non-HTTP(S) endpoint, non-2xxresponse, malformed JSON, or missing completion content with a clear error.
the extension does not parse or validate JSON in phase 1.
request; an error in a non-null row fails the statement.
llmextension and do not alterCREATE_EMBEDDINGor itsprovider classes.
HTTP I/O occur only when
AI_EXTRACTexecutes; loadingllmand executingexisting functions must preserve their current behavior.
dynamic LLM extension artifacts from loading for existing embedding use.
Concurrency, Scheduling, And Cancellation
Phase 1 adds a
TaskSchedulerscheduling entry point acceptingmain::ClientContext*. Scalar execution already exposes this object throughFunctionBindData::clientContext; the scheduler's current use ofExecutionContextis only to access that same object's interruption and timeoutstate. This narrow core interface avoids changing the general
scalar_func_exec_tsignature used by existing scalar functions and UDFs.
AI_EXTRACTgets theclient context from bind data, collects non-null rows for one vector chunk,
creates a bounded provider task, and runs it through
TaskScheduler. Parallelism is bounded bymin(4, ClientContext::getMaxNumThreadForExec(), row_count)and results retaintheir original row positions.
The completion HTTP layer uses libcurl rather than
cpp-httplib. Its progresscallback checks query interruption and deadline and aborts an active transfer.
All scheduled work must finish or be terminated before scalar execution returns;
no extension-owned background worker may outlive the invocation. Existing
CREATE_EMBEDDINGand itscpp-httplibproviders remain unchanged.Acceptance Criteria
choices[0].message.contentparsing,null input, bad key, non-2xx response, malformed response, and batch ordering.
result, and error behavior are unchanged after
AI_EXTRACTis added.can load and run an existing embedding test without a system libcurl runtime.
the two Cypher examples above persist the expected strings.