Module: PWN::AI::Ollama
- Defined in:
- lib/pwn/ai/ollama.rb
Overview
Direct client for a local/remote Ollama server REST API. No API key is required for a stock ollama serve (http://127.0.0.1:11434). Paths are native Ollama:
GET /api/tags
POST /api/chat (native tool_calls, options.num_ctx / num_predict)
POST /api/embed
POST /v1/chat/completions (OpenAI-compat shim)
Spec: https://github.com/ollama/ollama/blob/main/docs/api.md
Constant Summary collapse
- DEFAULT_BASE_URI =
'http://127.0.0.1:11434'
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.chat(opts = {}) ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.chat( request: 'required - message to Ollama' model: 'optional - model to use for text generation (defaults to PWN::Env[:ollama][:model])', temp: 'optional - creative response float (defaults to PWN::Env[:ollama][:temp])', system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ollama][:system_role_content])', response_history: 'optional - pass response back in to have a conversation', speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)', timeout: 'optional timeout in seconds (defaults to 900)', spinner: 'optional - display spinner (defaults to false)' ).
-
.chat_with_tools(opts = {}) ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.chat_with_tools( messages: 'required - full OpenAI-format messages array (system/user/assistant/tool)', tools: 'optional - OpenAI tools array [function:{...}]', tool_choice: 'optional - "auto" | "none" | function:{name:..}', model: 'optional - overrides PWN::Env[:ollama][:model]', temp: 'optional - temperature (defaults to PWN::Env[:ollama][:temp] || 1)', timeout: 'optional - seconds (default 900)', spinner: 'optional - display spinner (default false)' ).
-
.get_models ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.get_models.
-
.get_plan_usage(opts = {}) ⇒ Object
- Supported Method Parameters
usage = PWN::AI::Ollama.get_plan_usage.
-
.help ⇒ Object
Display Usage for this Module.
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
643 644 645 646 647 |
# File 'lib/pwn/ai/ollama.rb', line 643 public_class_method def self. "AUTHOR(S): 0day Inc. <support@0dayinc.com> " end |
.chat(opts = {}) ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.chat( request: 'required - message to Ollama' model: 'optional - model to use for text generation (defaults to PWN::Env[:ollama][:model])', temp: 'optional - creative response float (defaults to PWN::Env[:ollama][:temp])', system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ollama][:system_role_content])', response_history: 'optional - pass response back in to have a conversation', speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)', timeout: 'optional timeout in seconds (defaults to 900)', spinner: 'optional - display spinner (defaults to false)' )
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 |
# File 'lib/pwn/ai/ollama.rb', line 550 public_class_method def self.chat(opts = {}) engine = PWN::Env[:ai][:ollama] request = opts[:request] max_prompt_length = engine[:max_prompt_length] ||= 1_000_000 request_trunc_idx = ((max_prompt_length - 1) / 3.36).floor request = request[0..request_trunc_idx] model = opts[:model] ||= engine[:model] raise 'ERROR: Model is required. Call #get_models method for details' if model.nil? temp = opts[:temp].to_f ||= engine[:temp].to_f temp = 1 if temp.zero? # Native Ollama chat. Stock ollama serve and many reverse proxies # expose /api/chat, not the OpenAI-compat shim /v1/chat/completions # (that path 404s when the shim is disabled or not mounted). rest_call = 'api/chat' response_history = opts[:response_history] system_role_content = opts[:system_role_content] ||= engine[:system_role_content] system_role = { role: 'system', content: system_role_content } user_role = { role: 'user', content: request } response_history ||= { choices: [system_role] } num_ctx = (engine[:num_ctx] || 32_768).to_i num_predict = (engine[:num_predict] || 4_096).to_i keep_alive = engine[:keep_alive] || '30m' http_body = { model: model, messages: [system_role], stream: true, keep_alive: keep_alive, options: { num_ctx: num_ctx, num_predict: num_predict, temperature: temp } } if response_history[:choices].length > 1 response_history[:choices][1..-1].each do || http_body[:messages].push() end end http_body[:messages].push(user_role) timeout = opts[:timeout] spinner = opts[:spinner] response = ollama_rest_call( http_method: :post, rest_call: rest_call, http_body: http_body, timeout: timeout, spinner: spinner ) json_resp = JSON.parse(response, symbolize_names: true) assistant_resp = json_resp[:message] || json_resp.dig(:choices, 0, :message) raise "ERROR: Ollama chat response missing message/choices: #{json_resp.inspect[0, 400]}" if assistant_resp.nil? json_resp[:choices] = http_body[:messages] json_resp[:choices].push(assistant_resp) speak_answer = true if opts[:speak_answer] if speak_answer answer = assistant_resp[:content] text_path = "/tmp/#{SecureRandom.hex}.pwn_voice" File.write(text_path, answer) PWN::Plugins::Voice.text_to_speech(text_path: text_path) File.unlink(text_path) end json_resp rescue StandardError => e raise e end |
.chat_with_tools(opts = {}) ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.chat_with_tools( messages: 'required - full OpenAI-format messages array (system/user/assistant/tool)', tools: 'optional - OpenAI tools array [function:{...}]', tool_choice: 'optional - "auto" | "none" | function:{name:..}', model: 'optional - overrides PWN::Env[:ollama][:model]', temp: 'optional - temperature (defaults to PWN::Env[:ollama][:temp] || 1)', timeout: 'optional - seconds (default 900)', spinner: 'optional - display spinner (default false)' )
Hits Ollama NATIVE POST /api/chat so options.num_ctx / num_predict / keep_alive take effect. Streaming is ON; ollama_rest_call assembles NDJSON chunks back into a single response.
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 |
# File 'lib/pwn/ai/ollama.rb', line 477 public_class_method def self.chat_with_tools(opts = {}) engine = PWN::Env[:ai][:ollama] = (messages: opts[:messages]) raise 'ERROR: messages array is required' if .nil? || .empty? model = opts[:model] ||= engine[:model] raise 'ERROR: Model is required. Call #get_models method for details' if model.nil? temp = opts[:temp].to_f temp = engine[:temp].to_f.nonzero? || 1 if temp.zero? tools_present = opts[:tools] && !opts[:tools].empty? tool_temp = (engine[:tool_temp] || 0.1).to_f num_ctx = (engine[:num_ctx] || 32_768).to_i num_predict = (engine[:num_predict] || 4_096).to_i keep_alive = engine[:keep_alive] || '30m' http_body = { model: model, messages: , stream: true, keep_alive: keep_alive, options: { num_ctx: num_ctx, num_predict: num_predict, temperature: tools_present ? tool_temp : temp } } if tools_present http_body[:tools] = opts[:tools] fmt = engine[:format] http_body[:format] = fmt unless fmt.nil? || fmt.to_s.empty? end http_body[:tool_choice] = opts[:tool_choice] if opts[:tool_choice] response = ollama_rest_call( http_method: :post, rest_call: 'api/chat', http_body: http_body, timeout: opts[:timeout], spinner: opts[:spinner] ) raise 'ERROR: Ollama chat_with_tools received empty response from ollama_rest_call' if response.nil? || (response.respond_to?(:empty?) && response.empty?) json_resp = JSON.parse(response, symbolize_names: true) msg = json_resp[:message] || json_resp.dig(:choices, 0, :message) if msg.is_a?(Hash) content = msg[:content].to_s thinking = msg[:thinking].to_s tcalls = Array(msg[:tool_calls]) msg = msg.merge(content: visible_from_thinking(thinking: thinking)) if content.strip.empty? && !thinking.strip.empty? && tcalls.empty? end json_resp[:choices] = [{ message: msg }] if msg && !json_resp.key?(:choices) json_resp[:assistant_message] = msg raise "ERROR: Ollama response missing message/choices: #{json_resp.inspect[0, 400]}" if msg.nil? json_resp rescue StandardError => e raise e end |
.get_models ⇒ Object
- Supported Method Parameters
response = PWN::AI::Ollama.get_models
365 366 367 368 369 370 371 |
# File 'lib/pwn/ai/ollama.rb', line 365 public_class_method def self.get_models models = ollama_rest_call(rest_call: 'api/tags') JSON.parse(models, symbolize_names: true)[:models] rescue StandardError => e raise e end |
.get_plan_usage(opts = {}) ⇒ Object
- Supported Method Parameters
usage = PWN::AI::Ollama.get_plan_usage
Local Ollama has no subscription / plan-usage endpoint. Always unavailable so the PS1 shows the infinity glyph.
378 379 380 381 |
# File 'lib/pwn/ai/ollama.rb', line 378 public_class_method def self.get_plan_usage(opts = {}) _unused = opts { available: false, engine: :ollama, unlimited: true } end |
.help ⇒ Object
Display Usage for this Module
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 |
# File 'lib/pwn/ai/ollama.rb', line 651 public_class_method def self.help puts "USAGE: models = #{self}.get_models usage = #{self}.get_plan_usage response = #{self}.chat( request: 'required - message to Ollama', model: 'optional - model to use for text generation (defaults to PWN::Env[:ai][:ollama][:model])', temp: 'optional - creative response float (defaults to PWN::Env[:ai][:ollama][:temp])', system_role_content: 'optional - context to set up the model behavior for conversation (Default: PWN::Env[:ai][:ollama][:system_role_content])', response_history: 'optional - pass response back in to have a conversation', speak_answer: 'optional speak answer using PWN::Plugins::Voice.text_to_speech (Default: nil)', timeout: 'optional - timeout in seconds (defaults to 900)', spinner: 'optional - display spinner (defaults to false)' ) response = #{self}.chat_with_tools( messages: 'required - messages array', tools: 'optional - OpenAI tools array', model: 'optional - overrides PWN::Env[:ai][:ollama][:model]' ) #{self}.authors " end |