Skip to content

Commit 73d8488

Browse files
authored
feat: Teams AI V2 Python (batch 1) (#14614)
* feat: default bot in python * feat: default bot in python * feat: default bot in python * feat: basic agent in python * feat: customize data * feat: customize data * feat: customize data * feat: me * feat: me * feat: custom api 1 * feat: custom api 1 * feat: custom api 1 * feat: custom api * refactor: more * update readme * refactor: more * feat: more * feat: more * feat: more
1 parent 41e687e commit 73d8488

File tree

124 files changed

+5369
-598
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+5369
-598
lines changed

packages/fx-core/src/component/generator/openApiSpec/helper.ts

Lines changed: 82 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,9 +1209,7 @@ async function updatePromptForCustomApi(
12091209
spec.info.description ? ". " + spec.info.description : "."
12101210
}\nIf the API doesn't require parameters, invoke it with default JSON object ${
12111211
(language as ProgrammingLanguage) === ProgrammingLanguage.CSharp ? cSharpObject : object
1212-
}.\n\n${
1213-
shouldGenerateTeamsAIV2Code(language) ? "" : "context:\nAvailable actions: {{getAction}}."
1214-
}`;
1212+
}.\n\n`;
12151213
await fs.writeFile(promptFilePath, prompt, { encoding: "utf-8", flag: "w" });
12161214
}
12171215
}
@@ -1305,24 +1303,13 @@ function filterSchema(schema: OpenAPIV3.SchemaObject): OpenAPIV3.SchemaObject {
13051303
return filteredSchema;
13061304
}
13071305

1308-
function shouldGenerateTeamsAIV2Code(language: string) {
1309-
return (
1310-
language === ProgrammingLanguage.JS ||
1311-
language === ProgrammingLanguage.TS ||
1312-
language === ProgrammingLanguage.CSharp
1313-
);
1314-
}
1315-
13161306
async function updateActionForCustomApi(
13171307
specItems: SpecObject[],
13181308
language: string,
13191309
chatFolder: string
13201310
): Promise<void> {
13211311
if (commonLanguages.includes(language as ProgrammingLanguage)) {
1322-
const actionsFilePath = path.join(
1323-
chatFolder,
1324-
shouldGenerateTeamsAIV2Code(language) ? "functions.json" : "actions.json"
1325-
);
1312+
const actionsFilePath = path.join(chatFolder, "functions.json");
13261313
const actions = [];
13271314

13281315
for (const item of specItems) {
@@ -1380,74 +1367,21 @@ async function updateActionForCustomApi(
13801367
});
13811368
}
13821369

1383-
if (shouldGenerateTeamsAIV2Code(language)) {
1384-
// Convert actions array to object format for Teams AI v2
1385-
const actionsObject: { [key: string]: any } = {};
1386-
for (const action of actions) {
1387-
if (action.name) {
1388-
actionsObject[action.name] = {
1389-
name: action.name,
1390-
description: action.description,
1391-
parameters: action.parameters,
1392-
};
1393-
}
1370+
// Convert actions array to object format for Teams AI v2
1371+
const actionsObject: { [key: string]: any } = {};
1372+
for (const action of actions) {
1373+
if (action.name) {
1374+
actionsObject[action.name] = {
1375+
name: action.name,
1376+
description: action.description,
1377+
parameters: action.parameters,
1378+
};
13941379
}
1395-
await fs.writeFile(actionsFilePath, JSON.stringify(actionsObject, null, 2));
1396-
} else {
1397-
await fs.writeFile(actionsFilePath, JSON.stringify(actions, null, 2));
13981380
}
1381+
await fs.writeFile(actionsFilePath, JSON.stringify(actionsObject, null, 2));
13991382
}
14001383
}
14011384

1402-
const ActionCode = {
1403-
python: `
1404-
@bot_app.ai.action("{{operationId}}")
1405-
async def {{operationId}}(
1406-
context: ActionTurnContext[Dict[str, Any]],
1407-
state: AppTurnState,
1408-
):
1409-
parameters = context.data
1410-
path = parameters.get("path", {})
1411-
body = parameters.get("body", None)
1412-
query = parameters.get("query", {})
1413-
resp = client.{{operationId}}(**path, json=body, _headers={}, _params=query, _cookies={})
1414-
1415-
if resp.status_code != 200:
1416-
await context.send_activity(resp.reason)
1417-
else:
1418-
card_template_path = os.path.join(current_dir, 'adaptiveCards/{{operationId}}.json')
1419-
if not os.path.exists(card_template_path):
1420-
json_resoponse_str = resp.text
1421-
await context.send_activity(json_resoponse_str)
1422-
else:
1423-
with open(card_template_path) as card_template_file:
1424-
adaptive_card_template = card_template_file.read()
1425-
1426-
renderer = AdaptiveCardRenderer(adaptive_card_template)
1427-
1428-
json_resoponse_str = resp.text
1429-
rendered_card_str = renderer.render(json_resoponse_str)
1430-
rendered_card_json = json.loads(rendered_card_str)
1431-
card = CardFactory.adaptive_card(rendered_card_json)
1432-
isTeamsChannel = context.activity.channel_id == "msteams"
1433-
message = MessageFactory.attachment(card)
1434-
message.entities = [
1435-
{
1436-
"type": "https://schema.org/Message",
1437-
"@type": "Message",
1438-
"@context": "https://schema.org",
1439-
"additionalType": ["AIGeneratedContent"],
1440-
},
1441-
]
1442-
message.channel_data = {
1443-
"feedbackLoopEnabled": isTeamsChannel
1444-
}
1445-
1446-
await context.send_activity(message)
1447-
return "success"
1448-
`,
1449-
};
1450-
14511385
const functionDefinitionCode = {
14521386
javascript: `.function(
14531387
functionDefs.{{operationId}}.name,
@@ -1483,6 +1417,14 @@ const functionDefinitionCode = {
14831417
prompt.Functions.Add(new Function(FunctionDefinitionLoader.FunctionDefinitions["{{operationId}}"]["name"].ToString(),
14841418
FunctionDefinitionLoader.FunctionDefinitions["{{operationId}}"]["description"].ToString(),
14851419
{{operationId}}Schema, (IDictionary<string, object?> args) => handlers.{{functionName}}(args).GetAwaiter().GetResult()));`,
1420+
python: `.with_function(
1421+
Function(
1422+
name=function_defs["{{operationId}}"]["name"],
1423+
description=function_defs["{{operationId}}"]["description"],
1424+
parameter_schema=function_defs["{{operationId}}"]["parameters"],
1425+
handler=make_handler({{operationId}}, ctx)
1426+
)
1427+
)`,
14861428
};
14871429

14881430
const functionHandlerCode = {
@@ -1567,6 +1509,32 @@ module.exports = { {{operationId}}Handler };`,
15671509
return "results are shown already. completed.";
15681510
}
15691511
`,
1512+
python: `async def {{operationId}}(
1513+
parameters,
1514+
):
1515+
path = getattr(parameters, "path", {})
1516+
body = getattr(parameters, "body", None)
1517+
query = getattr(parameters, "query", {})
1518+
resp = client.{{operationId}}(**path, json=body, _headers={}, _params=query, _cookies={})
1519+
1520+
if resp.status_code != 200:
1521+
return resp.reason
1522+
else:
1523+
card_template_path = os.path.join(current_dir, 'adaptiveCards/{{operationId}}.json')
1524+
if not os.path.exists(card_template_path):
1525+
json_resoponse_str = resp.text
1526+
return json_resoponse_str
1527+
else:
1528+
with open(card_template_path) as card_template_file:
1529+
adaptive_card_template = card_template_file.read()
1530+
1531+
renderer = AdaptiveCardRenderer(adaptive_card_template)
1532+
1533+
json_resoponse_str = resp.text
1534+
rendered_card_str = renderer.render(json_resoponse_str)
1535+
rendered_card_json = json.loads(rendered_card_str)
1536+
return AdaptiveCard.model_validate(rendered_card_json)
1537+
`,
15701538
};
15711539

15721540
const AuthCode = {
@@ -1631,23 +1599,42 @@ async function updateCodeForCustomApi(
16311599
.replace("// Replace with function handler code", functionHandlersCode.join("\t\t\n"));
16321600
await fs.writeFile(handlerFilePath, updatedHandlerFileContent);
16331601
} else if (language === ProgrammingLanguage.PY) {
1634-
// Update code in bot.py
1635-
const actionsCode = [];
1636-
const codeTemplate = ActionCode["python"];
1602+
const appFolderPath = path.join(destinationPath, "src");
1603+
const functionDefinitionTemplate = functionDefinitionCode["python"];
1604+
const functionHandlerTemplate = functionHandlerCode["python"];
1605+
1606+
const appFilePath = path.join(appFolderPath, "app.py");
1607+
const handlerFilePath = path.join(appFolderPath, "handlers.py");
1608+
const functionDefinitionsCode = [];
1609+
const functionHandlersCode = [];
1610+
const operationIds: string[] = [];
16371611
for (const item of specItems) {
1638-
const code = codeTemplate
1639-
.replace(/{{operationId}}/g, item.item.operationId!)
1640-
.replace(/{{pathUrl}}/g, item.pathUrl)
1641-
.replace(/{{method}}/g, item.method);
1642-
actionsCode.push(code);
1612+
functionDefinitionsCode.push(
1613+
functionDefinitionTemplate.replace(/{{operationId}}/g, item.item.operationId!)
1614+
);
1615+
functionHandlersCode.push(
1616+
functionHandlerTemplate
1617+
.replace(/{{operationId}}/g, item.item.operationId!)
1618+
.replace(/{{pathUrl}}/g, item.pathUrl)
1619+
.replace(/{{method}}/g, item.method)
1620+
);
1621+
operationIds.push(`${item.item.operationId!}`);
16431622
}
16441623

1645-
const botFilePath = path.join(destinationPath, "src", "bot.py");
1646-
const botFileContent = (await fs.readFile(botFilePath)).toString();
1647-
const updateBotFileContent = botFileContent
1624+
const appFileContent = (await fs.readFile(appFilePath)).toString();
1625+
const updatedAppFileContent = appFileContent
1626+
.replace(
1627+
"// Replace with function definition code",
1628+
`agent${functionDefinitionsCode.join("")}`
1629+
)
1630+
.replace("//Replace with functions to be imported", `${operationIds.join(", ")}`);
1631+
await fs.writeFile(appFilePath, updatedAppFileContent);
1632+
1633+
const handlerFileContent = (await fs.readFile(handlerFilePath)).toString();
1634+
const updatedHandlerFileContent = handlerFileContent
16481635
.replace("{{OPENAPI_SPEC_PATH}}", openapiSpecFileName)
1649-
.replace("# Replace with action code", actionsCode.join("\n"));
1650-
await fs.writeFile(botFilePath, updateBotFileContent);
1636+
.replace("// Replace with function handler code", functionHandlersCode.join("\t\t\n"));
1637+
await fs.writeFile(handlerFilePath, updatedHandlerFileContent);
16511638
} else if (language === ProgrammingLanguage.CSharp) {
16521639
const functionDefinitionsCode = [];
16531640
const functionHandlersCode = [];
@@ -1710,21 +1697,16 @@ export async function updateForCustomApi(
17101697
openapiSpecFileName: string
17111698
): Promise<WarningResult[]> {
17121699
const warnings: WarningResult[] = [];
1713-
let chatFolder = shouldGenerateTeamsAIV2Code(language)
1714-
? path.join(destinationPath, "src", "app")
1715-
: path.join(destinationPath, "src", "prompts", "chat");
1700+
let chatFolder = path.join(destinationPath, "src", "app");
17161701
if (language === ProgrammingLanguage.CSharp) {
17171702
chatFolder = path.join(destinationPath, "Functions");
1703+
} else if (language === ProgrammingLanguage.PY) {
1704+
chatFolder = path.join(destinationPath, "src");
17181705
}
17191706
await fs.ensureDir(chatFolder);
17201707

17211708
// 1. update prompt folder
1722-
await updatePromptForCustomApi(
1723-
spec,
1724-
language,
1725-
chatFolder,
1726-
shouldGenerateTeamsAIV2Code(language) ? "instructions.txt" : "skprompt.txt"
1727-
);
1709+
await updatePromptForCustomApi(spec, language, chatFolder, "instructions.txt");
17281710

17291711
const [specItems, needAuth] = parseSpec(spec);
17301712

packages/fx-core/src/component/generator/templates/metadata/special.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ export const specialTemplates: Template[] = [
4848
language: "csharp",
4949
description: "",
5050
},
51-
// {
52-
// id: "custom-copilot-rag-custom-api-python",
53-
// name: TemplateNames.CustomCopilotRagCustomApi,
54-
// language: "python",
55-
// description: "",
56-
// },
51+
{
52+
id: "teams-agent-with-data-custom-api-v2-python",
53+
name: TemplateNames.CustomCopilotRagCustomApi,
54+
language: "python",
55+
description: "",
56+
},
5757
{
5858
id: "message-extension-with-existing-api",
5959
name: TemplateNames.MessageExtensionWithExistingApiSpec,

packages/fx-core/src/component/generator/templates/metadata/teams.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ const chatWithYourDataTemplates: Template[] = [
2323
language: "csharp",
2424
description: "",
2525
},
26+
{
27+
id: "custom-copilot-rag-customize-python",
28+
name: TemplateNames.CustomCopilotRagCustomize,
29+
language: "python",
30+
description: "",
31+
},
2632
{
2733
id: "custom-copilot-rag-azure-ai-search-ts",
2834
name: TemplateNames.CustomCopilotRagAzureAISearch,
@@ -116,12 +122,24 @@ const teamsOtherTemplates: Template[] = [
116122
language: "javascript",
117123
description: "",
118124
},
125+
{
126+
id: "default-bot-python",
127+
name: TemplateNames.DefaultBot,
128+
language: "python",
129+
description: "",
130+
},
119131
{
120132
id: "message-extension-v2-ts",
121133
name: TemplateNames.DefaultMessageExtension,
122134
language: "typescript",
123135
description: "",
124136
},
137+
{
138+
id: "message-extension-v2-python",
139+
name: TemplateNames.DefaultMessageExtension,
140+
language: "python",
141+
description: "",
142+
},
125143
// VS templates below
126144
{
127145
id: "basic-tab-csharp",
@@ -198,6 +216,12 @@ export const teamsAgentsAndAppsTemplates: Template[] = [
198216
language: "csharp",
199217
description: "",
200218
},
219+
{
220+
id: "custom-copilot-basic-python",
221+
name: TemplateNames.CustomCopilotBasic,
222+
language: "python",
223+
description: "",
224+
},
201225
...chatWithYourDataTemplates,
202226
{
203227
id: "teams-collaborator-agent-ts",

templates/vsc/python/custom-copilot-basic/README.md.tpl

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
# Overview of the Basic AI Chatbot template
22

3-
This app template is built on top of [Teams AI library](https://aka.ms/teams-ai-library).
3+
This app template is built on top of [Teams AI library V2](https://aka.ms/teams-ai-library-v2).
44
This template showcases an agent app that responds to user questions like an AI assistant. This enables your users to talk with the AI assistant in Teams to find information.
55

6-
> **Note**
7-
>
8-
> [Teams AI library V2](https://aka.ms/teams-ai-library-v2) is recommended. This template will be upgraded to Teams AI V2 soon.
9-
106
## Get started with the template
117

128
> **Prerequisites**
139
>
1410
> To run the template in your local dev machine, you will need:
1511
>
16-
> - [Python](https://www.python.org/), version 3.8 to 3.11.
12+
> - [Python](https://www.python.org/) v3.12 or higher.
1713
> - [Python extension](https://code.visualstudio.com/docs/languages/python), version v2024.0.1 or higher.
1814
> - [Microsoft 365 Agents Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) latest version or [Microsoft 365 Agents Toolkit CLI](https://aka.ms/teams-toolkit-cli).
1915
{{#useAzureOpenAI}}
@@ -61,11 +57,9 @@ The following files can be customized and demonstrate an example implementation
6157

6258
| File | Contents |
6359
| - | - |
64-
|`src/app.py`| Hosts an aiohttp api server and exports an app module.|
65-
|`src/bot.py`| Handles business logics for the Basic AI Chatbot.|
60+
|`src/app.py`| Handles business logics for the Basic AI Chatbot.|
6661
|`src/config.py`| Defines the environment variables.|
67-
|`src/prompts/chat/skprompt.txt`| Defines the prompt.|
68-
|`src/prompts/chat/config.json`| Configures the prompt.|
62+
|`src/instructions.txt`| Defines the prompt.|
6963

7064
The following are Microsoft 365 Agents Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Microsoft 365 Agents Toolkit works.
7165

@@ -77,13 +71,7 @@ The following are Microsoft 365 Agents Toolkit specific project files. You can [
7771

7872
## Extend the template
7973

80-
You can follow [Build a Basic AI Chatbot in Teams](https://aka.ms/teamsfx-basic-ai-chatbot) to extend the Basic AI Chatbot template with more AI capabilities, like:
81-
- [Customize prompt](https://aka.ms/teamsfx-basic-ai-chatbot#customize-prompt)
82-
- [Customize user input](https://aka.ms/teamsfx-basic-ai-chatbot#customize-user-input)
83-
- [Customize conversation history](https://aka.ms/teamsfx-basic-ai-chatbot#customize-conversation-history)
84-
- [Customize model type](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-type)
85-
- [Customize model parameters](https://aka.ms/teamsfx-basic-ai-chatbot#customize-model-parameters)
86-
- [Handle messages with image](https://aka.ms/teamsfx-basic-ai-chatbot#handle-messages-with-image)
74+
To extend the Basic AI Chatbot template with more AI capabilities, explore [Teams AI library V2 documentation](https://aka.ms/m365-agents-toolkit/teams-agent-extend-ai-python).
8775

8876
## Additional information and references
8977

0 commit comments

Comments
 (0)