152 lines
5.8 KiB
Python
152 lines
5.8 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
from unittest.mock import patch
|
|
|
|
from odoo import Command
|
|
from odoo.tests import TransactionCase, tagged
|
|
|
|
|
|
@tagged("post_install", "-at_install")
|
|
class TestAIAgent(TransactionCase):
|
|
|
|
def test_close_chat_only_works_with_ai_channel(self):
|
|
partner = self.env["res.partner"].create({
|
|
"name": "Odoo AI"
|
|
})
|
|
|
|
agent = self.env["ai.agent"].create({
|
|
"name": "Odoo AI",
|
|
"partner_id": partner.id
|
|
|
|
})
|
|
ai_chat_channel = agent._get_or_create_ai_chat()
|
|
regular_channel = self.env["discuss.channel"].create({
|
|
"channel_member_ids": [
|
|
Command.create(
|
|
{
|
|
"partner_id": self.env.user.partner_id.id,
|
|
}
|
|
),
|
|
],
|
|
"channel_type": "chat",
|
|
"name": "Non AI chat"
|
|
})
|
|
|
|
agent.close_chat(ai_chat_channel.id)
|
|
agent.close_chat(regular_channel.id)
|
|
self.assertFalse(ai_chat_channel.exists(), "Channel of type 'ai_chat' should be deleted when closed.")
|
|
self.assertTrue(regular_channel.exists(), "Only channels in ['ai_chat', 'ai_composer'] should be deleted on close.")
|
|
|
|
def test_generate_response_no_duplicate_user_prompt(self):
|
|
"""Test that user prompts are not duplicated when calling _generate_response"""
|
|
agent = self.env["ai.agent"].create({
|
|
"name": "Test AI Agent",
|
|
"llm_model": "gpt-4",
|
|
})
|
|
channel = agent._get_or_create_ai_chat()
|
|
|
|
# First 2 messages to simulate first question and response
|
|
channel.message_post(
|
|
body="first question",
|
|
author_id=self.env.user.partner_id.id,
|
|
message_type='comment',
|
|
)
|
|
channel.message_post(
|
|
body="Sure, I'm here to help. What's your first question?",
|
|
author_id=agent.partner_id.id,
|
|
message_type='comment',
|
|
)
|
|
|
|
# Post a new message and followed by an explicit generate response call.
|
|
# In the generate response, we check that the user prompt is not duplicated.
|
|
channel.message_post(
|
|
body="second question",
|
|
author_id=self.env.user.partner_id.id,
|
|
message_type='comment',
|
|
)
|
|
with patch('odoo.addons.ai.utils.llm_api_service.LLMApiService._request') as mock_request, \
|
|
patch('odoo.addons.ai.utils.llm_api_service.LLMApiService._get_api_token') as mock_token:
|
|
mock_token.return_value = 'test-api-key'
|
|
mock_request.return_value = {
|
|
'output': [{
|
|
'type': 'message',
|
|
'content': [{
|
|
'type': 'text',
|
|
'text': 'Response to second question',
|
|
}],
|
|
}],
|
|
}
|
|
|
|
agent._generate_response(prompt="second question")
|
|
|
|
mock_request.assert_called_once()
|
|
call_kwargs = mock_request.call_args.kwargs
|
|
messages = call_kwargs['body']['input']
|
|
|
|
user_messages = [msg for msg in messages if msg.get('role') == 'user']
|
|
second_question_count = sum(1 for msg in user_messages if msg.get('content') == 'second question')
|
|
|
|
self.assertEqual(second_question_count, 1, "User prompt 'second question' should appear only once in messages")
|
|
|
|
def test_get_or_create_ai_chat_returns_single_channel(self):
|
|
"""Ensure _get_or_create_ai_chat always returns a single channel even if multiple exist."""
|
|
agent = self.env["ai.agent"].create({
|
|
"name": "Test Agent",
|
|
})
|
|
# create the first AI chat channel
|
|
agent._get_or_create_ai_chat()
|
|
|
|
# manually create a second AI chat channel for the same agent
|
|
self.env["discuss.channel"].create({
|
|
"channel_member_ids": [
|
|
Command.create({"partner_id": self.env.user.partner_id.id}),
|
|
],
|
|
"channel_type": "ai_chat",
|
|
"ai_agent_id": agent.id,
|
|
"name": "Extra AI chat",
|
|
})
|
|
|
|
# call again: should return a single record
|
|
channel = agent._get_or_create_ai_chat()
|
|
self.assertEqual(len(channel), 1, "Should always return exactly one AI chat channel")
|
|
|
|
def test_action_ask_ai_always_opens_a_new_channel(self):
|
|
"""Ensure action_ask_ai always opens a new ai chat channel."""
|
|
agent = self.env["ai.agent"].create({"name": "Odoo AI"})
|
|
|
|
action_1 = agent.action_ask_ai("Hello, AI!")
|
|
action_2 = agent.action_ask_ai("Hello, AI, again!")
|
|
|
|
self.assertLess(
|
|
action_1["params"]["channelId"],
|
|
action_2["params"]["channelId"],
|
|
"Each call to 'action_ask_ai' should open a new ai chat channel."
|
|
)
|
|
|
|
def test_get_llm_response_with_sources(self):
|
|
"""Responses include source links when the LLM returns attachment ids."""
|
|
agent = self.env["ai.agent"].create({
|
|
"name": "Odoo Agent",
|
|
})
|
|
|
|
attachment = self.env["ir.attachment"].create({
|
|
"name": "Doc 1",
|
|
"raw": b"content",
|
|
"mimetype": "text/plain",
|
|
})
|
|
self.env["ai.agent.source"].create({
|
|
"name": "Doc 1",
|
|
"agent_id": agent.id,
|
|
"attachment_id": attachment.id,
|
|
"type": "binary",
|
|
"status": "indexed",
|
|
"is_active": True,
|
|
})
|
|
|
|
message = f"Here is your answer [SOURCE:{attachment.id}]"
|
|
llm_response = agent._get_llm_response_with_sources([message])
|
|
|
|
self.assertEqual(len(llm_response), 1)
|
|
self.assertNotIn("[SOURCE", llm_response[0])
|
|
self.assertIn("href=\"%s/web/content/%s\"" % (agent.get_base_url(), attachment.id), llm_response[0])
|
|
self.assertIn("[1]", llm_response[0])
|