aboutsummaryrefslogtreecommitdiffstats
BranchCommit messageAuthorAge
mainFOP-3236: Add all the needed namespaces to the parent elementJoao Goncalves7 days
jgoncalves-pdfa-extension-fixFOP-3236: Add all the needed namespaces to the parent elementJoao Goncalves9 days
temp/ssteiner-java23Build Java 23 without spotbugsSimon Steiner4 months
temp/ssteiner-pdfbox3FOP-3203: Upgrade to PDFBox 3Simon Steiner5 months
fop-2_10Update for releaseSimon Steiner5 months
fop-2_9Update versionSimon Steiner19 months
skynavga/configure-sonar-supportAdd git ignores (#71)Glenn Adams24 months
fop-2_8Update for releasessteiner2 years
fop-2_7Update for releaseSimon Steiner3 years
fop-2_6Update for releaseSimon Steiner4 years
fop-2.6BranchSimon Steiner4 years
fop-2_5Update for releaseSimon Steiner5 years
fop-2_4Update to release versionSimon Steiner5 years
Temp_ChangeBars2FOP-1760: FindbugsSimon Steiner7 years
fop-2_3Update to release versionSimon Steiner7 years
Temp_SurrogatePairsFix javadocsSimon Steiner7 years
fop-2_2Update to batik RCSimon Steiner8 years
mavenMerge ^/xmlgraphics/fop/trunk.Glenn Adams9 years
fop-2_1FOP-2560: PDF to PDF corrupt after reuse of FopFactorySimon Steiner9 years
Temp_PCLSoftFontsFOP-2486 - Enhancements and fixes to existing patchRobert Meyer10 years
fop-2_0Correct versions in POMSimon Steiner10 years
Temp_PDFLinearizationFOP-2445: PDF Linearization, fix imagesSimon Steiner10 years
Temp_BasicSideFloatshandle lists when ipd changes across pagesLuis Bernardo10 years
FOP-2393_gradient-renderingRemoved unused codeVincent Hennebert11 years
Temp_FontMergingAdd t1 mergingSimon Steiner11 years
Temp_WhitespaceManagementBrought the branch in sync with rev. 1590916 of trunkVincent Hennebert11 years
Temp_FopFontsForSVGUpdated FindBugs excludesVincent Hennebert11 years
Temp_InlineContainerBrought the branch in sync with rev. r1559284 of trunkVincent Hennebert11 years
Temp_RoundedCornersRemoved a CastPeter Hancock12 years
archiveArchive old branch.Glenn Adams12 years
fop-1_1Merge changes from trunk through revision 1400690; update XGC lib with new po...Glenn Adams12 years
Temp_MergeFrom11Temporary branch to aid in merge from 1.1 branch.Glenn Adams12 years
Temp_XGC_URI_ResolutionMerged trunk@1384277Mehdi Houshmand12 years
Temp_Gradle_BuildAdded hyphenation JAR taskMehdi Houshmand12 years
fop-1_1oldRename existing fop-1_1 branch prior to creating a new branch that contains c...Glenn Adams13 years
Temp_URI_UnificationDeleted some svn:mergeinfo propertiesMehdi Houshmand13 years
fop-1_1rc1Merge from origin/trunk.Glenn Adams13 years
Temp_TrueTypeInPostScriptMerged changes from trunk up to revision 1351540Vincent Hennebert13 years
Temp_PDF_ObjectStreamsAdded support for PDF object streams.Vincent Hennebert13 years
Temp_CFFintegrate (very) preliminary cff supportGlenn Adams13 years
Temp_ImproveAccessibilityAddressed findbugs and checkstyle regressionsPeter Hancock13 years
Temp_ComplexScriptsPatch #5 by Glenn Adams; see Bugzilla 49687Simon Pepping13 years
Temp_ColorFix deprecation.Jeremias Maerki14 years
fop-1_0Adding the build dependencies to the source distributionSimon Pepping15 years
Temp_ChangeBarsCreated temporary branch for change bar implementationVincent Hennebert15 years
Temp_AccessibilityMerged changes from Trunk up to revision 830265Vincent Hennebert15 years
Temp_ChangingIPDHackBlocked revision 808135 from TrunkVincent Hennebert16 years
Temp_AreaTreeNewDesignTrying to work around a problem reintegrating the branch. Found a blog entry ...Jeremias Maerki16 years
Temp_AFPGOCAResources* Text encoding is now handled by PresentationTextData and character set enco...Adrian Cumiskey16 years
Temp_Interleaved_Page_Line_BreakingFixed the 4 lines vs 5 lines example; incorrect indentation was making the ca...Vincent Hennebert16 years
fop-0_95Added missing support for Next Line (NEL, 0x0085) and Line Separator (LS, 0x2...Jeremias Maerki16 years
Temp_ProcessingFeedbackMerged revisions 642144-647679 via svnmerge from Jeremias Maerki17 years
Temp_AFPAffineTransformspelling correctionAdrian Cumiskey17 years
fop-0_93Hotfix for 0.93: Jeremias Maerki17 years
Temp_ImagePackageRedesignRemoved empty package.Jeremias Maerki17 years
Temp_PDF_in_PDFBugfix: Make sure floats and doubles are not formatted using exponential form...Jeremias Maerki17 years
fop-0_94Removed merge tracking for "svnmerge" for Vincent Hennebert18 years
Temp_FloatsPatch for the implementation of the improved float-placement algorithmVincent Hennebert18 years
foray-fontre-enabled generated ToUnicode tableBertrand Delacretaz18 years
fop-0_92Almost forgot the .htaccess file to manage all the redirects.Jeremias Maerki19 years
Temp_API_FinalizationRemoving unreachable catch block.Jeremias Maerki19 years
fop-0_20_2-maintainOh well, let's remove the "enum" tokens entirely. Maybe Gump is happier this ...Jeremias Maerki19 years
fop-0_91Update Release Branch.Jeremias Maerki19 years
fop-0_90Documented additional known issue with PS Renderer that I didn't have time to...Jeremias Maerki19 years
Temp_SpaceResolutionFix for the two problems raised by Simon: Jeremias Maerki19 years
inlineblockNow that the whole breakability inside inlines is reenabled, the original ele...Jeremias Maerki20 years
Temp_KnuthStylePageBreakingReactivating table-row background.Jeremias Maerki20 years
FOP_0-20-0_Alt-DesignMods to make scripts independent of any particular jars in lib.Peter Bernard West20 years
FOP_0-20-4Pre_BuildExp_pbwThis commit was manufactured by cvs2svn to create branch(no author)23 years
fop-0_17_0_batikSVGcompiles with current batikKeiron Liddle24 years
fop-0_14_0_regionsNew pagination examplearved24 years
fop-0_14_0Compatibility with JDK 1.1.xarved25 years
release-0-13-0fix: initial-page-number started with page number 1 at each page sequencefotis25 years
dirkxInitial importDirk-Willem van Gulik25 years
 
TagDownloadAuthorAge
2_10xmlgraphics-fop-2_10.tar.gz  xmlgraphics-fop-2_10.zip  Simon Steiner5 months
2_9xmlgraphics-fop-2_9.tar.gz  xmlgraphics-fop-2_9.zip  Simon Steiner19 months
fop-2_8xmlgraphics-fop-fop-2_8.tar.gz  xmlgraphics-fop-fop-2_8.zip  ssteiner2 years
fop-2_7xmlgraphics-fop-fop-2_7.tar.gz  xmlgraphics-fop-fop-2_7.zip  Simon Steiner3 years
fop-2_6xmlgraphics-fop-fop-2_6.tar.gz  xmlgraphics-fop-fop-2_6.zip  Simon Steiner4 years
fop-2_5xmlgraphics-fop-fop-2_5.tar.gz  xmlgraphics-fop-fop-2_5.zip  Simon Steiner5 years
fop-2_4xmlgraphics-fop-fop-2_4.tar.gz  xmlgraphics-fop-fop-2_4.zip  Simon Steiner5 years
fop-2_3xmlgraphics-fop-fop-2_3.tar.gz  xmlgraphics-fop-fop-2_3.zip  Simon Steiner7 years
fop-2_2xmlgraphics-fop-fop-2_2.tar.gz  xmlgraphics-fop-fop-2_2.zip  Simon Steiner8 years
fop-2_1xmlgraphics-fop-fop-2_1.tar.gz  xmlgraphics-fop-fop-2_1.zip  Simon Steiner9 years
fop-2_0xmlgraphics-fop-fop-2_0.tar.gz  xmlgraphics-fop-fop-2_0.zip  Simon Steiner10 years
fop-1_1xmlgraphics-fop-fop-1_1.tar.gz  xmlgraphics-fop-fop-1_1.zip  Glenn Adams12 years
fop-1_1old1xmlgraphics-fop-fop-1_1old1.tar.gz  xmlgraphics-fop-fop-1_1old1.zip  Glenn Adams12 years
fop-1_1rc1xmlgraphics-fop-fop-1_1rc1.tar.gz  xmlgraphics-fop-fop-1_1rc1.zip  Glenn Adams13 years
fop-1_1rc1oldxmlgraphics-fop-fop-1_1rc1old.tar.gz  xmlgraphics-fop-fop-1_1rc1old.zip  Glenn Adams13 years
fop-1_0xmlgraphics-fop-fop-1_0.tar.gz  xmlgraphics-fop-fop-1_0.zip  Vincent Hennebert14 years
fop-0_95xmlgraphics-fop-fop-0_95.tar.gz  xmlgraphics-fop-fop-0_95.zip  Jeremias Maerki17 years
fop-0_95betaxmlgraphics-fop-fop-0_95beta.tar.gz  xmlgraphics-fop-fop-0_95beta.zip  Vincent Hennebert17 years
fop-0_94xmlgraphics-fop-fop-0_94.tar.gz  xmlgraphics-fop-fop-0_94.zip  Vincent Hennebert18 years
fop-0_93xmlgraphics-fop-fop-0_93.tar.gz  xmlgraphics-fop-fop-0_93.zip  Simon Pepping18 years
fop-0_92-betaxmlgraphics-fop-fop-0_92-beta.tar.gz  xmlgraphics-fop-fop-0_92-beta.zip  Jeremias Maerki19 years
fop-0_91-betaxmlgraphics-fop-fop-0_91-beta.tar.gz  xmlgraphics-fop-fop-0_91-beta.zip  Jeremias Maerki19 years
fop-0_90-alpha1xmlgraphics-fop-fop-0_90-alpha1.tar.gz  xmlgraphics-fop-fop-0_90-alpha1.zip  Christian Geisert19 years
Before_Merge_KnuthStylePageBreaking_Back_To_HEADxmlgraphics-fop-Before_Merge_KnuthStylePageBreaking_Back_To_HEAD.tar.gz  xmlgraphics-fop-Before_Merge_KnuthStylePageBreaking_Back_To_HEAD.zip  (no author)20 years
Before_Merge_KnuthStylePageBreaking_Branchxmlgraphics-fop-Before_Merge_KnuthStylePageBreaking_Branch.tar.gz  xmlgraphics-fop-Before_Merge_KnuthStylePageBreaking_Branch.zip  (no author)20 years
batik-1_6xmlgraphics-fop-batik-1_6.tar.gz  xmlgraphics-fop-batik-1_6.zip  (no author)20 years
Root_Temp_KnuthStylePageBreakingxmlgraphics-fop-Root_Temp_KnuthStylePageBreaking.tar.gz  xmlgraphics-fop-Root_Temp_KnuthStylePageBreaking.zip  (no author)20 years
Defoe_exportxmlgraphics-fop-Defoe_export.tar.gz  xmlgraphics-fop-Defoe_export.zip  (no author)20 years
Alt-Design_pre_awt_renderer_importxmlgraphics-fop-Alt-Design_pre_awt_renderer_import.tar.gz  xmlgraphics-fop-Alt-Design_pre_awt_renderer_import.zip  (no author)21 years
fop-0_20_5xmlgraphics-fop-fop-0_20_5.tar.gz  xmlgraphics-fop-fop-0_20_5.zip  (no author)22 years
Alt-Design_pre_src-java-orgxmlgraphics-fop-Alt-Design_pre_src-java-org.tar.gz  xmlgraphics-fop-Alt-Design_pre_src-java-org.zip  (no author)22 years
fop-0_20_5rc3axmlgraphics-fop-fop-0_20_5rc3a.tar.gz  xmlgraphics-fop-fop-0_20_5rc3a.zip  (no author)22 years
fop-0_20_5rc3xmlgraphics-fop-fop-0_20_5rc3.tar.gz  xmlgraphics-fop-fop-0_20_5rc3.zip  (no author)22 years
Alt-Design-integration-basexmlgraphics-fop-Alt-Design-integration-base.tar.gz  xmlgraphics-fop-Alt-Design-integration-base.zip  (no author)22 years
fop-0_20_5rc2xmlgraphics-fop-fop-0_20_5rc2.tar.gz  xmlgraphics-fop-fop-0_20_5rc2.zip  (no author)22 years
fop-0_20_5rcxmlgraphics-fop-fop-0_20_5rc.tar.gz  xmlgraphics-fop-fop-0_20_5rc.zip  (no author)22 years
Alt-Design_pre_Properties_splitxmlgraphics-fop-Alt-Design_pre_Properties_split.tar.gz  xmlgraphics-fop-Alt-Design_pre_Properties_split.zip  (no author)22 years
fop-0_20_4xmlgraphics-fop-fop-0_20_4.tar.gz  xmlgraphics-fop-fop-0_20_4.zip  (no author)23 years
fop-0_20_4-docxmlgraphics-fop-fop-0_20_4-doc.tar.gz  xmlgraphics-fop-fop-0_20_4-doc.zip  (no author)23 years
FOP_Alt-Design_Migrationxmlgraphics-fop-FOP_Alt-Design_Migration.tar.gz  xmlgraphics-fop-FOP_Alt-Design_Migration.zip  (no author)23 years
fop-0_20_3xmlgraphics-fop-fop-0_20_3.tar.gz  xmlgraphics-fop-fop-0_20_3.zip  (no author)23 years
fop-0_20_0xmlgraphics-fop-fop-0_20_0.tar.gz  xmlgraphics-fop-fop-0_20_0.zip  (no author)24 years
PRE_CODEFORMATTINGxmlgraphics-fop-PRE_CODEFORMATTING.tar.gz  xmlgraphics-fop-PRE_CODEFORMATTING.zip  (no author)24 years
fop-0_18_1xmlgraphics-fop-fop-0_18_1.tar.gz  xmlgraphics-fop-fop-0_18_1.zip  (no author)24 years
fop-0_17_0xmlgraphics-fop-fop-0_17_0.tar.gz  xmlgraphics-fop-fop-0_17_0.zip  (no author)24 years
pre-columnsxmlgraphics-fop-pre-columns.tar.gz  xmlgraphics-fop-pre-columns.zip  (no author)24 years
RELEASE_0_12_0xmlgraphics-fop-RELEASE_0_12_0.tar.gz  xmlgraphics-fop-RELEASE_0_12_0.zip  (no author)25 years
RELEASE_0_12_0_pre_5xmlgraphics-fop-RELEASE_0_12_0_pre_5.tar.gz  xmlgraphics-fop-RELEASE_0_12_0_pre_5.zip  (no author)25 years
initialxmlgraphics-fop-initial.tar.gz  xmlgraphics-fop-initial.zip  (no author)25 years
et_displayed_text_part(task) if not sel_part then return false, 'no text part found' end -- Check limits and size sanity local nwords = sel_part:get_words_count() if nwords < 5 then return false, 'less than 5 words' end if nwords > settings.max_tokens then -- We need to truncate words (sometimes get_words_count returns a different number comparing to `get_words`) local words = sel_part:get_words('norm') nwords = #words if nwords > settings.max_tokens then return true, table.concat(words, ' ', 1, settings.max_tokens) end end return true, sel_part:get_content_oneline() end local function maybe_extract_json(str) -- Find the first opening brace local startPos, endPos = str:find('json%s*{') if not startPos then startPos, endPos = str:find('{') end if not startPos then return nil end startPos = endPos - 1 local openBraces = 0 endPos = startPos local len = #str -- Iterate through the string to find matching braces for i = startPos, len do local char = str:sub(i, i) if char == "{" then openBraces = openBraces + 1 elseif char == "}" then openBraces = openBraces - 1 -- When we find the matching closing brace if openBraces == 0 then endPos = i break end end end -- If we found a complete JSON-like structure if openBraces == 0 then return str:sub(startPos, endPos) end return nil end local function default_conversion(task, input) local parser = ucl.parser() local res, err = parser:parse_string(input) if not res then rspamd_logger.errx(task, 'cannot parse reply: %s', err) return end local reply = parser:get_object() if not reply then rspamd_logger.errx(task, 'cannot get object from reply') return end if type(reply.choices) ~= 'table' or type(reply.choices[1]) ~= 'table' then rspamd_logger.errx(task, 'no choices in reply') return end local first_message = reply.choices[1].message.content if not first_message then rspamd_logger.errx(task, 'no content in the first message') return end -- Apply heuristic to extract JSON first_message = maybe_extract_json(first_message) or first_message parser = ucl.parser() res, err = parser:parse_string(first_message) if not res then rspamd_logger.errx(task, 'cannot parse JSON gpt reply: %s', err) return end reply = parser:get_object() if type(reply) == 'table' and reply.probability then lua_util.debugm(N, task, 'extracted probability: %s', reply.probability) local spam_score = tonumber(reply.probability) if not spam_score then -- Maybe we need GPT to convert GPT reply here? if reply.probability == "high" then spam_score = 0.9 elseif reply.probability == "low" then spam_score = 0.1 else rspamd_logger.infox("cannot convert to spam probability: %s", reply.probability) end end if type(reply.usage) == 'table' then rspamd_logger.infox(task, 'usage: %s tokens', reply.usage.total_tokens) end return spam_score, reply.reason end rspamd_logger.errx(task, 'cannot convert spam score: %s', first_message) return end local function ollama_conversion(task, input) local parser = ucl.parser() local res, err = parser:parse_string(input) if not res then rspamd_logger.errx(task, 'cannot parse reply: %s', err) return end local reply = parser:get_object() if not reply then rspamd_logger.errx(task, 'cannot get object from reply') return end if type(reply.message) ~= 'table' then rspamd_logger.errx(task, 'bad message in reply') return end local first_message = reply.message.content if not first_message then rspamd_logger.errx(task, 'no content in the first message') return end -- Apply heuristic to extract JSON first_message = maybe_extract_json(first_message) or first_message parser = ucl.parser() res, err = parser:parse_string(first_message) if not res then rspamd_logger.errx(task, 'cannot parse JSON gpt reply: %s', err) return end reply = parser:get_object() if type(reply) == 'table' and reply.probability then lua_util.debugm(N, task, 'extracted probability: %s', reply.probability) local spam_score = tonumber(reply.probability) if not spam_score then -- Maybe we need GPT to convert GPT reply here? if reply.probability == "high" then spam_score = 0.9 elseif reply.probability == "low" then spam_score = 0.1 else rspamd_logger.infox("cannot convert to spam probability: %s", reply.probability) end end if type(reply.usage) == 'table' then rspamd_logger.infox(task, 'usage: %s tokens', reply.usage.total_tokens) end return spam_score, reply.reason end rspamd_logger.errx(task, 'cannot convert spam score: %s', first_message) return end local function check_consensus(task, results) for _, result in ipairs(results) do if not result.checked then return end end local nspam, nham = 0, 0 local max_spam_prob, max_ham_prob = 0, 0 local reasons = {} for _, result in ipairs(results) do if result.success then if result.probability > 0.5 then nspam = nspam + 1 max_spam_prob = math.max(max_spam_prob, result.probability) lua_util.debugm(N, task, "model: %s; spam: %s; reason: '%s'", result.model, result.probability, result.reason) else nham = nham + 1 max_ham_prob = math.min(max_ham_prob, result.probability) lua_util.debugm(N, task, "model: %s; ham: %s; reason: '%s'", result.model, result.probability, result.reason) end if result.reason then table.insert(reasons, result.reason) end end end lua_util.shuffle(reasons) local reason = reasons[1] or nil if nspam > nham and max_spam_prob > 0.75 then task:insert_result('GPT_SPAM', (max_spam_prob - 0.75) * 4, tostring(max_spam_prob)) if settings.autolearn then task:set_flag("learn_spam") end if reason and settings.reason_header then lua_mime.modify_headers(task, { add = { [settings.reason_header] = { value = 'value', order = 1 } } }) end elseif nham > nspam and max_ham_prob < 0.25 then task:insert_result('GPT_HAM', (0.25 - max_ham_prob) * 4, tostring(max_ham_prob)) if settings.autolearn then task:set_flag("learn_ham") end if reason and settings.reason_header then lua_mime.modify_headers(task, { add = { [settings.reason_header] = { value = 'value', order = 1 } } }) end else -- No consensus lua_util.debugm(N, task, "no consensus") end end local function get_meta_llm_content(task) local url_content = "Url domains: no urls found" if task:has_urls() then local urls = lua_util.extract_specific_urls { task = task, limit = 5, esld_limit = 1 } url_content = "Url domains: " .. table.concat(fun.totable(fun.map(function(u) return u:get_tld() or '' end, urls or {})), ', ') end local from_or_empty = ((task:get_from('mime') or E)[1] or E) local from_content = string.format('From: %s <%s>', from_or_empty.name, from_or_empty.addr) lua_util.debugm(N, task, "gpt urls: %s", url_content) lua_util.debugm(N, task, "gpt from: %s", from_content) return url_content, from_content end local function default_llm_check(task) local ret, content = settings.condition(task) if not ret then rspamd_logger.info(task, "skip checking gpt as the condition is not met: %s", content) return end if not content then lua_util.debugm(N, task, "no content to send to gpt classification") return end lua_util.debugm(N, task, "sending content to gpt: %s", content) local upstream local results = {} local function gen_reply_closure(model, idx) return function(err, code, body) results[idx].checked = true if err then rspamd_logger.errx(task, '%s: request failed: %s', model, err) upstream:fail() check_consensus(task, results) return end upstream:ok() lua_util.debugm(N, task, "%s: got reply: %s", model, body) if code ~= 200 then rspamd_logger.errx(task, 'bad reply: %s', body) return end local reply, reason = settings.reply_conversion(task, body) results[idx].model = model if reply then results[idx].success = true results[idx].probability = reply results[idx].reason = reason end check_consensus(task, results) end end local from_content, url_content = get_meta_llm_content(task) local body = { model = settings.model, max_tokens = settings.max_tokens, temperature = settings.temperature, messages = { { role = 'system', content = settings.prompt }, { role = 'user', content = 'Subject: ' .. task:get_subject() or '', }, { role = 'user', content = from_content, }, { role = 'user', content = url_content, }, { role = 'user', content = content } } } -- Conditionally add response_format if settings.include_response_format then body.response_format = { type = "json_object" } end if type(settings.model) == 'string' then settings.model = { settings.model } end upstream = settings.upstreams:get_upstream_round_robin() for idx, model in ipairs(settings.model) do results[idx] = { success = false, checked = false } body.model = model local http_params = { url = settings.url, mime_type = 'application/json', timeout = settings.timeout, log_obj = task, callback = gen_reply_closure(model, idx), headers = { ['Authorization'] = 'Bearer ' .. settings.api_key, }, keepalive = true, body = ucl.to_format(body, 'json-compact', true), task = task, upstream = upstream, use_gzip = true, } if not rspamd_http.request(http_params) then results[idx].checked = true end end end local function ollama_check(task) local ret, content = settings.condition(task) if not ret then rspamd_logger.info(task, "skip checking gpt as the condition is not met: %s", content) return end if not content then lua_util.debugm(N, task, "no content to send to gpt classification") return end lua_util.debugm(N, task, "sending content to gpt: %s", content) local upstream local results = {} local function gen_reply_closure(model, idx) return function(err, code, body) results[idx].checked = true if err then rspamd_logger.errx(task, '%s: request failed: %s', model, err) upstream:fail() check_consensus(task, results) return end upstream:ok() lua_util.debugm(N, task, "%s: got reply: %s", model, body) if code ~= 200 then rspamd_logger.errx(task, 'bad reply: %s', body) return end local reply, reason = settings.reply_conversion(task, body) results[idx].model = model if reply then results[idx].success = true results[idx].probability = reply results[idx].reason = reason end check_consensus(task, results) end end local from_content, url_content = get_meta_llm_content(task) if type(settings.model) == 'string' then settings.model = { settings.model } end local body = { stream = false, model = settings.model, max_tokens = settings.max_tokens, temperature = settings.temperature, messages = { { role = 'system', content = settings.prompt }, { role = 'user', content = 'Subject: ' .. task:get_subject() or '', }, { role = 'user', content = from_content, }, { role = 'user', content = url_content, }, { role = 'user', content = content } } } for i, model in ipairs(settings.model) do -- Conditionally add response_format if settings.include_response_format then body.response_format = { type = "json_object" } end body.model = model upstream = settings.upstreams:get_upstream_round_robin() local http_params = { url = settings.url, mime_type = 'application/json', timeout = settings.timeout, log_obj = task, callback = gen_reply_closure(model, i), keepalive = true, body = ucl.to_format(body, 'json-compact', true), task = task, upstream = upstream, use_gzip = true, } rspamd_http.request(http_params) end end local function gpt_check(task) return settings.specific_check(task) end local types_map = { openai = { check = default_llm_check, condition = default_condition, conversion = default_conversion, require_passkey = true, }, ollama = { check = ollama_check, condition = default_condition, conversion = ollama_conversion, require_passkey = false, }, } local opts = rspamd_config:get_all_opt('gpt') if opts then settings = lua_util.override_defaults(settings, opts) if not settings.prompt then settings.prompt = "You will be provided with the email message, subject, from and url domains, " .. "and your task is to evaluate the probability to be spam as number from 0 to 1, " .. "output result as JSON with 'probability' field and " .. "add 'reason' field with 1 sentence description why you have made that decision." end if not settings.symbols_to_except then settings.symbols_to_except = default_symbols_to_except end local llm_type = types_map[settings.type] if not llm_type then rspamd_logger.warnx(rspamd_config, 'unsupported gpt type: %s', settings.type) lua_util.disable_module(N, "config") return end settings.specific_check = llm_type.check if settings.condition then settings.condition = load(settings.condition)() else settings.condition = llm_type.condition end if settings.reply_conversion then settings.reply_conversion = load(settings.reply_conversion)() else settings.reply_conversion = llm_type.conversion end if not settings.api_key and llm_type.require_passkey then rspamd_logger.warnx(rspamd_config, 'no api_key is specified for LLM type %s, disabling module', settings.type) lua_util.disable_module(N, "config") return end settings.upstreams = lua_util.http_upstreams_by_url(rspamd_config:get_mempool(), settings.url) local id = rspamd_config:register_symbol({ name = 'GPT_CHECK', type = 'postfilter', callback = gpt_check, priority = lua_util.symbols_priorities.medium, augmentations = { string.format("timeout=%f", settings.timeout or 0.0) }, }) rspamd_config:register_symbol({ name = 'GPT_SPAM', type = 'virtual', parent = id, score = 5.0, }) rspamd_config:register_symbol({ name = 'GPT_HAM', type = 'virtual', parent = id, score = -2.0, }) end