Skip to content

Feature/images input - #5

Open
GiN2K wants to merge 15 commits into
mainfrom
feature/ImagesInput
Open

GiN2K wants to merge 15 commits into
mainfrom
feature/ImagesInput

Conversation

@GiN2K

@GiN2K GiN2K commented Sep 10, 2026

Copy link
Copy Markdown

Base information

Question Answer
Related to a SourceForge thread / Another PR / A GitHub Issue / Combodo ticket?
Type of change? Bug fix / Enhancement / Translations

Symptom (bug) / Objective (enhancement)

Reproduction procedure (bug)

  1. On iTop x.y.z
  2. With module version x.y.z
  3. With PHP x.y.z
  4. First go there
  5. Then do that
  6. ...
  7. Finally, see that...

Cause (bug)

Proposed solution (bug and enhancement)

Checklist before requesting a review

  • If my PR is a bug fix or an enhancement, I opened an issue that got reviewed and approved by Combodo
  • I have performed a self-review of my code
  • I have tested all changes I made on an iTop instance
  • Would a unit test be relevant and have I added it?
  • Is the PR clear and detailed enough so anyone can understand without digging in the code?

GiN2K and others added 14 commits August 6, 2026 17:34
* feat: add embedding engine and service layer (itomig-de#50)

Adds an embedding layer contributed by Amine Ait-Ali (Combodo).
Rebased onto current main; autoload regenerated with --no-dev.

* chore(deps): update LLPhant to 1.0.0 (itomig-de#61)

Adapts the single call site to the renamed ChatInterface method
(generateChatOrReturnFunctionCalled -> generateChatOrReturnFunctionToCall).
No functional changes to itomig-ai-base; unified LLPhant tool-call
semantics fix double tool execution for Anthropic and Ollama.

* chore(deps): update LLPhant to 1.0.1
…ion (itomig-de#66)

* fix(phpstan): add missing IssueLog::Info and DBObject::GetKey stubs

The stub file used for standalone analysis was incomplete: IssueLog::Info()
and DBObject::GetKey() both exist in iTop 3.2 (core/log.class.inc.php:728,
core/dbobject.class.php:1596) but were absent from the attrappes, so every
call site was reported as an undefined method.

Removes three false positives, including a pre-existing one in
AIObjectTools.php:90. No production code is affected.

* feat(guardrail): add iAIGuardrail extension point for content moderation

AI-Base processes content that may be attacker-controlled, and hands model
output back to sinks that are not always reviewed by a human. It had no
content moderation and no place to add any.

Introduce iAIGuardrail, discovered via InterfaceDiscovery like the existing
tool provider contract, so an extension can screen AI content without any
calling code changing. AIService consults it at three points:

  - input:       the prompt, resp. the latest user turn of a conversation
  - output:      the model's answer, before it reaches the caller
  - tool_result: a tool's return value, before it re-enters the history
                 (the arrival point for indirect prompt injection)

Callers may declare their context via the new AIService::setSurface() so a
guardrail can apply a context-specific policy set. Omitting it keeps the
'default' surface.

Two properties are deliberate. IsEnabledFor() is consulted before every
check and must stay cheap, so callers without a guardrail pay nothing.
Guardrail failures are fail-open: an exception from an implementation is
logged and the AI call proceeds, since a guardrail outage must not take
down every AI feature. Blocking is expressed by returning a verdict, which
surfaces to the caller as AIGuardrailBlockedException.

Discovery is cached statically because several tool providers construct a
fresh AIService per call.

Purely additive: with no guardrail installed, behaviour is unchanged.

A Shieldstral-based implementation ships separately as itomig-ai-guardrail.

* feat(guardrail): screen the assembled system prompt and pass the iTop context

Adds DIRECTION_SYSTEM_PROMPT and screens the system prompt in GetCompletion()
and ContinueConversation(), before the input it governs.

The configured system prompt is a trusted source; what it has become by the time
it is sent is not necessarily. Two things change it at runtime. Placeholder
substitution: PerformSystemInstruction() already applies sprintf() to the
'translate' instruction, and a general placeholder feature would widen that to
values resolved from the database. And consumers appending data: ai-response
appends ticket JSON to the prompt, including a public log that may contain text
arriving by mail-to-ticket. Neither was screened.

The prompt is constant for the duration of a call, so it is checked once per
call rather than per turn or per tool round.

Also passes the active iTop context tags to the guardrail as
$aContext['itop_context'], read from ContextTag::GetStack(). This answers a
different question than the caller-declared surface: 'ticket.summarize' may be
triggered by an agent in the console or by a background job with nobody
reviewing the result. An empty stack is a normal state — not every entry point
sets a tag — and guardrails must treat it as "channel unknown".

IsEnabledFor() gains the context as a fourth parameter so that the decision
whether to screen at all can depend on the channel, for example screening
everything under GUI:Portal but only output under CRON.

BREAKING for iAIGuardrail implementations: PHP requires an implementation to
declare every parameter of an interface method, including optional ones, so
IsEnabledFor() implementations must be updated. No implementation exists in a
released version of this module; the reference implementation in
itomig-ai-guardrail is adjusted separately.

No change for consumers of AIService: no existing signature is altered, and
AIGuardrailBlockedException remains within the established hierarchy. A caller
can distinguish a prompt problem from a content problem via GetDirection().
…he suite

PHPUnit includes every test file while assembling the suite, before any
test bootstrap runs. The named top-level class RecordingGuardrail forced
PHP to resolve iAIGuardrail at file-load time, when the extension
autoloader (registered via env-production in setUp) is not yet available,
aborting the whole suite with 'Interface not found'.

The fake is now an anonymous class built by a factory method at test
runtime, matching the pattern already used in FunctionCallingTest. The
fake also records the context argument in Check(), which
testItopContextIsPassedToGuardrail asserts on but the previous fake
never captured.

Verified against a fresh iTop 3.3.0-dev install: all 7 suites pass
(64 tests, 370 assertions).
Copilot AI balanced review requested due to automatic review settings September 10, 2026 09:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Image validation gaps can permit URL payloads or forward non-image data to providers.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds image inputs to AI conversations through vision-capable OpenAI and Anthropic engines.

Changes:

  • Introduces vision engine contracts and provider-specific message conversion.
  • Classifies unsupported-vision responses and adds tests.
  • Documents image payloads and refreshes module/autoload metadata.
File summaries
File Description
src/Contracts/iAIVisionEngine.php Defines vision-message capability.
src/Engine/OpenAIEngine.php Builds OpenAI vision messages.
src/Engine/AnthropicAIEngine.php Builds Anthropic vision messages.
src/Engine/GenericAIEngine.php Classifies vision capability errors.
src/Service/AIService.php Converts image-bearing history entries.
src/Exception/AIVisionUnsupportedException.php Adds a vision-specific exception.
tests/php-unit-tests/unitary-tests/VisionEngineMessageTest.php Tests provider message construction.
tests/php-unit-tests/unitary-tests/ImageInputConversationTest.php Tests conversation image handling.
tests/php-unit-tests/unitary-tests/GenericAIEngineExceptionTest.php Tests error classification.
tests/php-unit-tests/unitary-tests/GuardrailContractTest.php Applies formatting updates.
README.md Documents image input usage.
module.itomig-ai-base.php Updates version and syntax.
vendor/composer/autoload_static.php Registers new classes.
vendor/composer/autoload_classmap.php Registers new classes.
vendor/composer/platform_check.php Updates generated platform failure handling.
vendor/autoload.php Updates generated autoload failure handling.
vendor/composer/LICENSE Applies whitespace changes.
Review details
  • Files reviewed: 12/17 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +112 to +116
try {
$aAnthropicImages[] = new AnthropicImage(
AnthropicImageType::from($sMediaType),
$sData
);
Comment thread src/Engine/OpenAIEngine.php Outdated
Comment on lines +119 to +122
$aImageSources[] = new ImageSource(
$sData,
ImageQuality::High
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants